ソースを参照

Added more tests to toView testing. Added renderer to use with marked.js.

Szymon Kupś 9 年 前
コミット
d8448bece9
18 ファイル変更855 行追加217 行削除
  1. 4 2
      packages/ckeditor5-markdown-gfm/src/gfmdataprocessor.js
  2. 176 0
      packages/ckeditor5-markdown-gfm/src/renderer.js
  3. 0 12
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/_fixtures/blockquotes.js
  4. 0 22
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/_fixtures/code_blocks.js
  5. 0 77
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/_fixtures/lists.js
  6. 0 32
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/_fixtures/strong_and_emphasis.js
  7. 0 17
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/_fixtures/tabs.js
  8. 0 7
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/_fixtures/tidyness.js
  9. 37 0
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/blockquotes.js
  10. 49 0
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/code_blocks.js
  11. 31 0
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/del.js
  12. 0 48
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/gfmdataprocessor.js
  13. 67 0
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/headers.js
  14. 211 0
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/horizontal_rules.js
  15. 109 0
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/lists.js
  16. 61 0
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/paragraphs.js
  17. 61 0
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/strong_and_emphasis.js
  18. 49 0
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/tabs.js

+ 4 - 2
packages/ckeditor5-markdown-gfm/src/gfmdataprocessor.js

@@ -6,6 +6,7 @@
 import marked from './lib/marked.js';
 import toMarkdown from './lib/to-markdown.js';
 import HtmlDataProcessor from '../engine/dataprocessor/htmldataprocessor.js';
+import GFMRenderer from './renderer.js';
 
 export default class GFMDataProcessor extends HtmlDataProcessor {
 	constructor() {
@@ -16,10 +17,11 @@ export default class GFMDataProcessor extends HtmlDataProcessor {
 		const html = marked.parse( data, {
 			gfm: true,
 			breaks: true,
-			xhtml: true
+			xhtml: true,
+			renderer: new GFMRenderer()
 		} );
 
-		return super.toView( html.replace( /\n/g, '' ) );
+		return super.toView( html );
 	}
 
 	toData( viewFragment ) {

+ 176 - 0
packages/ckeditor5-markdown-gfm/src/renderer.js

@@ -0,0 +1,176 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * Marked.js library renderer with fixes:
+ * - no formatting for output HTML string - all newlines between tags are removed to create clean output,
+ * - changed long string concatenations to ES5 template strings,
+ * - changed code style.
+ *
+ * @param options
+ * @constructor
+ */
+function Renderer( options ) {
+	this.options = options || {};
+}
+
+Renderer.prototype.code = function( code, lang, escaped ) {
+	if ( this.options.highlight ) {
+		const out = this.options.highlight( code, lang );
+
+		if ( out !== null && out !== code ) {
+			escaped = true;
+			code = out;
+		}
+	}
+
+	if ( !lang ) {
+		return `<pre><code>${ escaped ? code : escape( code, true ) }</code></pre>`;
+	}
+
+	const cssClass = this.options.langPrefix + escape( lang, true );
+
+	return `<pre><code class="${ cssClass }">${ escaped ? code : escape( code, true ) }</code></pre>`;
+};
+
+Renderer.prototype.blockquote = function( quote ) {
+	return `<blockquote>${ quote }</blockquote>`;
+};
+
+Renderer.prototype.html = function( html ) {
+	return html;
+};
+
+Renderer.prototype.heading = function( text, level, raw ) {
+	const id = this.options.headerPrefix + raw.toLowerCase().replace( /[^\w]+/g, '-' );
+
+	return `<h${ level } id="${id}">${ text }</h${ level }>`;
+};
+
+Renderer.prototype.hr = function() {
+	return this.options.xhtml ? '<hr/>' : '<hr>';
+};
+
+Renderer.prototype.list = function( body, ordered ) {
+	const type = ordered ? 'ol' : 'ul';
+
+	return `<${ type }>${ body }</${ type }>`;
+};
+
+Renderer.prototype.listitem = function( text ) {
+	return `<li>${ text }</li>`;
+};
+
+Renderer.prototype.paragraph = function( text ) {
+	return `<p>${ text }</p>`;
+};
+
+Renderer.prototype.table = function( header, body ) {
+	return `<table><thead>${ header }</thead><tbody>${ body }</tbody></table>`;
+};
+
+Renderer.prototype.tablerow = function( content ) {
+	return '<tr>\n' + content + '</tr>\n';
+};
+
+Renderer.prototype.tablecell = function( content, flags ) {
+	const type = flags.header ? 'th' : 'td';
+	const tag = flags.align ? '<' + type + ' style="text-align:' + flags.align + '">' : '<' + type + '>';
+
+	return tag + content + '</' + type + '>\n';
+};
+
+// span level renderer
+Renderer.prototype.strong = function( text ) {
+	return `<strong>${ text }</strong>`;
+};
+
+Renderer.prototype.em = function( text ) {
+	return `<em>${ text }</em>`;
+};
+
+Renderer.prototype.codespan = function( text ) {
+	return `<code>${ text }</code>`;
+};
+
+Renderer.prototype.br = function() {
+	return this.options.xhtml ? '<br/>' : '<br>';
+};
+
+Renderer.prototype.del = function( text ) {
+	return `<del>${ text }</del>`;
+};
+
+Renderer.prototype.link = function( href, title, text ) {
+	if ( this.options.sanitize ) {
+		let prot;
+
+		try {
+			prot = decodeURIComponent( unescape( href ) )
+				.replace( /[^\w:]/g, '' )
+				.toLowerCase();
+		} catch ( e ) {
+			return '';
+		}
+
+		if ( prot.indexOf( 'javascript:' ) === 0 || prot.indexOf( 'vbscript:' ) === 0 ) {
+			return '';
+		}
+	}
+
+	let out = '<a href="' + href + '"';
+
+	if ( title ) {
+		out += ' title="' + title + '"';
+	}
+	out += '>' + text + '</a>';
+
+	return out;
+};
+
+Renderer.prototype.image = function( href, title, text ) {
+	let out = '<img src="' + href + '" alt="' + text + '"';
+
+	if ( title ) {
+		out += ' title="' + title + '"';
+	}
+	out += this.options.xhtml ? '/>' : '>';
+
+	return out;
+};
+
+Renderer.prototype.text = function( text ) {
+	return text;
+};
+
+export default Renderer;
+
+function escape( html, encode ) {
+	return html
+		.replace( !encode ? /&(?!#?\w+;)/g : /&/g, '&amp;' )
+		.replace( /</g, '&lt;' )
+		.replace( />/g, '&gt;' )
+		.replace( /"/g, '&quot;' )
+		.replace( /'/g, '&#39;' );
+}
+
+function unescape( html ) {
+	// explicitly match decimal, hex, and named HTML entities
+	return html.replace( /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/g, function( _, n ) {
+		n = n.toLowerCase();
+
+		if ( n === 'colon' ) {
+			return ':';
+		}
+
+		if ( n.charAt( 0 ) === '#' ) {
+			return n.charAt( 1 ) === 'x' ?
+				String.fromCharCode( parseInt( n.substring( 2 ), 16 ) ) :
+				String.fromCharCode( +n.substring( 1 ) );
+		}
+
+		return '';
+	} );
+}

+ 0 - 12
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/_fixtures/blockquotes.js

@@ -1,12 +0,0 @@
-export default [
-	{
-		name: 'single',
-		md: '> foo bar',
-		html: '<blockquote><p>foo bar</p></blockquote>'
-	},
-	{
-		name: 'nested',
-		md: '> foo\n>\n> > bar\n>\n> foo\n',
-		html: '<blockquote><p>foo</p><blockquote><p>bar</p></blockquote><p>foo</p></blockquote>'
-	}
-];

+ 0 - 22
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/_fixtures/code_blocks.js

@@ -1,22 +0,0 @@
-export default [
-	{
-		name: 'indented with tabs',
-		md: '	code block',
-		html: '<pre><code>code block</code></pre>'
-	},
-	{
-		name: 'indented with spaces',
-		md: '    code block',
-		html: '<pre><code>code block</code></pre>'
-	}
-	// {
-	// 	name: 'multiline',
-	// 	md: '	first line\n	second line',
-	// 	html: ''
-	// },
-	// {
-	// 	name: 'multiline with trailing spaces',
-	// 	md: '	the lines in this block  \n	all contain trailing spaces  ',
-	// 	html: '<pre><code>the lines in this blockall contain trailing spaces  </code></pre>'
-	// }
-];

+ 0 - 77
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/_fixtures/lists.js

@@ -1,77 +0,0 @@
-export default [
-	{
-		name: 'tight asterisks',
-		md: '*	item 1\n*	item 2\n*	item 3',
-		html: '<ul><li>item 1</li><li>item 2</li><li>item 3</li></ul>'
-	},
-	{
-		name: 'loose asterisks',
-		md: '*	item 1\n\n*	item 2\n\n*	item 3',
-		html: '<ul><li><p>item 1</p></li><li><p>item 2</p></li><li><p>item 3</p></li></ul>'
-	},
-	{
-		name: 'tight pluses',
-		md: '+	item 1\n+	item 2\n+	item 3',
-		html: '<ul><li>item 1</li><li>item 2</li><li>item 3</li></ul>'
-	},
-	{
-		name: 'loose pluses',
-		md: '+	item 1\n\n+	item 2\n\n+	item 3',
-		html: '<ul><li><p>item 1</p></li><li><p>item 2</p></li><li><p>item 3</p></li></ul>'
-	},
-	{
-		name: 'tight minuses',
-		md: '-	item 1\n-	item 2\n-	item 3',
-		html: '<ul><li>item 1</li><li>item 2</li><li>item 3</li></ul>'
-	},
-	{
-		name: 'loose minuses',
-		md: '-	item 1\n\n-	item 2\n\n-	item 3',
-		html: '<ul><li><p>item 1</p></li><li><p>item 2</p></li><li><p>item 3</p></li></ul>'
-	},
-	{
-		name: 'ordered list with tabs',
-		md: '1.	item 1\n2.	item 2\n3.	item 3',
-		html: '<ol><li>item 1</li><li>item 2</li><li>item 3</li></ol>'
-	},
-	{
-		name: 'ordered list with spaces',
-		md: '1. item 1\n2. item 2\n3. item 3',
-		html: '<ol><li>item 1</li><li>item 2</li><li>item 3</li></ol>'
-	},
-	{
-		name: 'loose ordered list with tabs',
-		md: '1.	item 1\n\n2.	item 2\n\n3.	item 3',
-		html: '<ol><li><p>item 1</p></li><li><p>item 2</p></li><li><p>item 3</p></li></ol>'
-	},
-	{
-		name: 'loose ordered list with spaces',
-		md: '1. item 1\n\n2. item 2\n\n3. item 3',
-		html: '<ol><li><p>item 1</p></li><li><p>item 2</p></li><li><p>item 3</p></li></ol>'
-	},
-	{
-		name: 'multiple paragraphs',
-		md: '1.	Item 1, graf one.\n\n	Item 2. graf two. The quick brown fox jumped over the lazy dogs\n	back.\n	\n2.	Item 2.\n\n3.	Item 3.',
-		html: '<ol><li><p>Item 1, graf one.</p><p>Item 2. graf two. The quick brown fox jumped over the lazy dogs<br></br>back.</p></li><li><p>Item 2.</p></li><li><p>Item 3.</p></li></ol>'
-	},
-	{
-		name: 'nested',
-		md: '*	Tab\n	*	Tab\n		*	Tab',
-		html: '<ul><li>Tab<ul><li>Tab<ul><li>Tab</li></ul></li></ul></li></ul>'
-	},
-	{
-		name: 'nested and mixed',
-		md: '1. First\n2. Second:\n	* Fee\n	* Fie\n	* Foe\n3. Third',
-		html: '<ol><li>First</li><li>Second:<ul><li>Fee</li><li>Fie</li><li>Foe</li></ul></li><li>Third</li></ol>'
-	},
-	{
-		name: 'nested and mixed loose',
-		md: '1. First\n\n2. Second:\n	* Fee\n	* Fie\n	* Foe\n\n3. Third',
-		html: '<ol><li><p>First</p></li><li><p>Second:</p><ul><li>Fee</li><li>Fie</li><li>Foe</li></ul></li><li><p>Third</p></li></ol>'
-	},
-	{
-		name: 'edge case - error in Markdown 1.0.1',
-		md: '*	this\n\n	*	sub\n\n	that',
-		html: '<ul><li><p>this</p><ul><li>sub</li></ul><p>that</p></li></ul>'
-	}
-];

+ 0 - 32
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/_fixtures/strong_and_emphasis.js

@@ -1,32 +0,0 @@
-export default [
-	{
-		name: 'should use strong',
-		md: '**this is strong** and __this too__',
-		html: '<p><strong>this is strong</strong> and <strong>this too</strong></p>'
-	},
-	{
-		name: 'should use emphasis',
-		md: '*this is emphasis* and _this too_',
-		html: '<p><em>this is emphasis</em> and <em>this too</em></p>'
-	},
-	{
-		name: 'should use strong and emphasis together #1',
-		md: '***This is strong and em.***',
-		html: '<p><strong><em>This is strong and em.</em></strong></p>'
-	},
-	{
-		name: 'should use strong and emphasis together #2',
-		md: 'Single ***word*** is strong and em.',
-		html: '<p>Single <strong><em>word</em></strong> is strong and em.</p>'
-	},
-	{
-		name: 'should use strong and emphasis together #2',
-		md: '___This is strong and em.___',
-		html: '<p><strong><em>This is strong and em.</em></strong></p>'
-	},
-	{
-		name: 'should use strong and emphasis together #2',
-		md: 'Single ___word___ is strong and em.',
-		html: '<p>Single <strong><em>word</em></strong> is strong and em.</p>'
-	}
-];

+ 0 - 17
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/_fixtures/tabs.js

@@ -1,17 +0,0 @@
-export default [
-	{
-		name: 'should create list item when using tabs',
-		md: '+	this is a list item indented with tabs\n',
-		html: '<ul><li>this is a list item indented with tabs</li></ul>'
-	},
-	{
-		name: 'should create list item when using spaces',
-		md: '+   this is a list item indented with spaces\n\n',
-		html: '<ul><li>this is a list item indented with spaces</li></ul>'
-	},
-	{
-		name: 'should create code block indented by tab',
-		md: '	this code block is indented by one tab',
-		html: '<pre><code>this code block is indented by one tab</code></pre>'
-	}
-];

+ 0 - 7
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/_fixtures/tidyness.js

@@ -1,7 +0,0 @@
-export default [
-	{
-		name: 'should create tidy output',
-		md: '> A list within a blockquote:\n> \n> *	asterisk 1\n> *	asterisk 2\n> *	asterisk 3\n',
-		html: '<blockquote><p>A list within a blockquote:</p><ul><li>asterisk 1</li><li>asterisk 2</li><li>asterisk 3</li></ul></blockquote>'
-	}
-];

+ 37 - 0
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/blockquotes.js

@@ -0,0 +1,37 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import MarkdownDataProcessor from '/ckeditor5/markdown-gfm/gfmdataprocessor.js';
+import { stringify } from '/tests/engine/_utils/view.js';
+
+describe( 'GFMDataProcessor', () => {
+	let dataProcessor;
+
+	beforeEach( () => {
+		dataProcessor = new MarkdownDataProcessor();
+	} );
+
+	describe( 'blockquotes', () => {
+		describe( 'toView', () => {
+			it( 'should process single blockquotes', () => {
+				const viewFragment = dataProcessor.toView( '> foo bar' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<blockquote><p>foo bar</p></blockquote>' );
+			} );
+
+			it( 'should process nested blockquotes', () => {
+				const viewFragment = dataProcessor.toView( '> foo\n>\n> > bar\n>\n> foo\n' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<blockquote><p>foo</p><blockquote><p>bar</p></blockquote><p>foo</p></blockquote>' );
+			} );
+
+			it( 'should process list within a blockquote', () => {
+				const viewFragment = dataProcessor.toView( '> A list within a blockquote:\n> \n> *	asterisk 1\n> *	asterisk 2\n> *	asterisk 3\n' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<blockquote><p>A list within a blockquote:</p><ul><li>asterisk 1</li><li>asterisk 2</li><li>asterisk 3</li></ul></blockquote>' );
+			} );
+		} );
+	} );
+} );

+ 49 - 0
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/code_blocks.js

@@ -0,0 +1,49 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import MarkdownDataProcessor from '/ckeditor5/markdown-gfm/gfmdataprocessor.js';
+import { stringify } from '/tests/engine/_utils/view.js';
+
+describe( 'GFMDataProcessor', () => {
+	let dataProcessor;
+
+	beforeEach( () => {
+		dataProcessor = new MarkdownDataProcessor();
+	} );
+
+	describe( 'code blocks', () => {
+		describe( 'toView', () => {
+			it( 'should process code blocks indented with tabs', () => {
+				const viewFragment = dataProcessor.toView( '	code block' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<pre><code>code block</code></pre>' );
+			} );
+
+			it( 'should process code blocks indented with spaces', () => {
+				const viewFragment = dataProcessor.toView( '    code block' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<pre><code>code block</code></pre>' );
+			} );
+
+			it( 'should process multi line code blocks indented with tabs', () => {
+				const viewFragment = dataProcessor.toView( '	first line\n	second line' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<pre><code>first line\nsecond line</code></pre>' );
+			} );
+
+			it( 'should process multi line code blocks indented with spaces', () => {
+				const viewFragment = dataProcessor.toView( '    first line\n    second line' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<pre><code>first line\nsecond line</code></pre>' );
+			} );
+
+			it( 'should process multi line code blocks with trailing spaces', () => {
+				const viewFragment = dataProcessor.toView( '	the lines in this block  \n	all contain trailing spaces  ' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<pre><code>the lines in this block  \nall contain trailing spaces  </code></pre>' );
+			} );
+		} );
+	} );
+} );

+ 31 - 0
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/del.js

@@ -0,0 +1,31 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import MarkdownDataProcessor from '/ckeditor5/markdown-gfm/gfmdataprocessor.js';
+import { stringify } from '/tests/engine/_utils/view.js';
+
+describe( 'GFMDataProcessor', () => {
+	let dataProcessor;
+
+	beforeEach( () => {
+		dataProcessor = new MarkdownDataProcessor();
+	} );
+
+	describe( 'del', () => {
+		describe( 'toView', () => {
+			it( 'should process deleted text', () => {
+				const viewFragment = dataProcessor.toView( '~~deleted~~' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<p><del>deleted</del></p>' );
+			} );
+
+			it( 'should process deleted inside text', () => {
+				const viewFragment = dataProcessor.toView( 'This is ~~deleted content~~.' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<p>This is <del>deleted content</del>.</p>' );
+			} );
+		} );
+	} );
+} );

+ 0 - 48
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/gfmdataprocessor.js

@@ -1,48 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-import MarkdownDataProcessor from '/ckeditor5/markdown-gfm/gfmdataprocessor.js';
-import { stringify } from '/tests/engine/_utils/view.js';
-
-// Fixtures
-import tidynes from '/tests/markdown-gfm/gfmdataprocessor/_fixtures/tidyness.js';
-import tabs from '/tests/markdown-gfm/gfmdataprocessor/_fixtures/tabs.js';
-import strongAndEmphasis from '/tests/markdown-gfm/gfmdataprocessor/_fixtures/strong_and_emphasis.js';
-import lists from '/tests/markdown-gfm/gfmdataprocessor/_fixtures/lists.js';
-import blockquotes from '/tests/markdown-gfm/gfmdataprocessor/_fixtures/blockquotes.js';
-import codeBlocks from '/tests/markdown-gfm/gfmdataprocessor/_fixtures/code_blocks.js';
-
-const fixturesSets = {
-	'strong and emphasis': strongAndEmphasis,
-	'lists': lists,
-	'block quotes': blockquotes,
-	'code blocks': codeBlocks,
-	'tabs': tabs,
-	'tidyness': tidynes
-};
-
-describe( 'GFMDataProcessor', () => {
-	let dataProcessor;
-
-	beforeEach( () => {
-		dataProcessor = new MarkdownDataProcessor();
-	} );
-
-	describe( 'toView', () => {
-		for ( let fixtureSetName in fixturesSets ) {
-			const fixtureSet = fixturesSets[ fixtureSetName ];
-
-			describe( fixtureSetName, () => {
-				for ( let fixture of fixtureSet ) {
-					it( fixture.name, () => {
-						const viewFragment = dataProcessor.toView( fixture.md );
-
-						expect( stringify( viewFragment ) ).to.equal( fixture.html );
-					} );
-				}
-			} );
-		}
-	} );
-} );

+ 67 - 0
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/headers.js

@@ -0,0 +1,67 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import MarkdownDataProcessor from '/ckeditor5/markdown-gfm/gfmdataprocessor.js';
+import { stringify } from '/tests/engine/_utils/view.js';
+
+describe( 'GFMDataProcessor', () => {
+	let dataProcessor;
+
+	beforeEach( () => {
+		dataProcessor = new MarkdownDataProcessor();
+	} );
+
+	describe( 'headers', () => {
+		describe( 'toView', () => {
+			it( 'level 1 header #1', () => {
+				const viewFragment = dataProcessor.toView( '# Level 1' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<h1 id="level-1">Level 1</h1>' );
+			} );
+
+			it( 'level 1 header #2', () => {
+				const viewFragment = dataProcessor.toView( 'Level 1\n===' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<h1 id="level-1">Level 1</h1>' );
+			} );
+
+			it( 'level 2 header #1', () => {
+				const viewFragment = dataProcessor.toView( '## Level 2' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<h2 id="level-2">Level 2</h2>' );
+			} );
+
+			it( 'level 2 header #2', () => {
+				const viewFragment = dataProcessor.toView( 'Level 2\n---' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<h2 id="level-2">Level 2</h2>' );
+			} );
+
+			it( 'level 3 header', () => {
+				const viewFragment = dataProcessor.toView( '### Level 3' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<h3 id="level-3">Level 3</h3>' );
+			} );
+
+			it( 'level 4 header', () => {
+				const viewFragment = dataProcessor.toView( '#### Level 4' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<h4 id="level-4">Level 4</h4>' );
+			} );
+
+			it( 'level 5 header', () => {
+				const viewFragment = dataProcessor.toView( '##### Level 5' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<h5 id="level-5">Level 5</h5>' );
+			} );
+
+			it( 'level 6 header', () => {
+				const viewFragment = dataProcessor.toView( '###### Level 6' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<h6 id="level-6">Level 6</h6>' );
+			} );
+		} );
+	} );
+} );

+ 211 - 0
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/horizontal_rules.js

@@ -0,0 +1,211 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import MarkdownDataProcessor from '/ckeditor5/markdown-gfm/gfmdataprocessor.js';
+import { stringify } from '/tests/engine/_utils/view.js';
+
+describe( 'GFMDataProcessor', () => {
+	let dataProcessor;
+
+	beforeEach( () => {
+		dataProcessor = new MarkdownDataProcessor();
+	} );
+
+	describe( 'horizontal rules', () => {
+		describe( 'toView', () => {
+			describe( 'dashes', () => {
+				it( '#1', () => {
+					const viewFragment = dataProcessor.toView( '---' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#2', () => {
+					const viewFragment = dataProcessor.toView( ' ---' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#3', () => {
+					const viewFragment = dataProcessor.toView( '  ---' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#4', () => {
+					const viewFragment = dataProcessor.toView( '   ---' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#5 - code', () => {
+					const viewFragment = dataProcessor.toView( '	---' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<pre><code>---</code></pre>' );
+				} );
+			} );
+
+			describe( 'dashes with spaces', () => {
+				it( '#1', () => {
+					const viewFragment = dataProcessor.toView( '- - -' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#2', () => {
+					const viewFragment = dataProcessor.toView( ' - - -' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#3', () => {
+					const viewFragment = dataProcessor.toView( '  - - -' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#4', () => {
+					const viewFragment = dataProcessor.toView( '   - - -' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#5 - code', () => {
+					const viewFragment = dataProcessor.toView( '	- - -' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<pre><code>- - -</code></pre>' );
+				} );
+			} );
+
+			describe( 'asterisks', () => {
+				it( '#1', () => {
+					const viewFragment = dataProcessor.toView( '***' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#2', () => {
+					const viewFragment = dataProcessor.toView( ' ***' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#3', () => {
+					const viewFragment = dataProcessor.toView( '  ***' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#4', () => {
+					const viewFragment = dataProcessor.toView( '   ***' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#5 - code', () => {
+					const viewFragment = dataProcessor.toView( '	***' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<pre><code>***</code></pre>' );
+				} );
+			} );
+
+			describe( 'asterisks with spaces', () => {
+				it( '#1', () => {
+					const viewFragment = dataProcessor.toView( '* * *' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#2', () => {
+					const viewFragment = dataProcessor.toView( ' * * *' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#3', () => {
+					const viewFragment = dataProcessor.toView( '  * * *' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#4', () => {
+					const viewFragment = dataProcessor.toView( '   * * *' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#5 - code', () => {
+					const viewFragment = dataProcessor.toView( '	* * *' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<pre><code>* * *</code></pre>' );
+				} );
+			} );
+
+			describe( 'underscores', () => {
+				it( '#1', () => {
+					const viewFragment = dataProcessor.toView( '___' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#2', () => {
+					const viewFragment = dataProcessor.toView( ' ___' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#3', () => {
+					const viewFragment = dataProcessor.toView( '  ___' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#4', () => {
+					const viewFragment = dataProcessor.toView( '   ___' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#5 - code', () => {
+					const viewFragment = dataProcessor.toView( '	___' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<pre><code>___</code></pre>' );
+				} );
+			} );
+
+			describe( 'underscores with spaces', () => {
+				it( '#1', () => {
+					const viewFragment = dataProcessor.toView( '_ _ _' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#2', () => {
+					const viewFragment = dataProcessor.toView( ' _ _ _' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#3', () => {
+					const viewFragment = dataProcessor.toView( '  _ _ _' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#4', () => {
+					const viewFragment = dataProcessor.toView( '   _ _ _' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<hr></hr>' );
+				} );
+
+				it( '#5 - code', () => {
+					const viewFragment = dataProcessor.toView( '	_ _ _' );
+
+					expect( stringify( viewFragment ) ).to.equal( '<pre><code>_ _ _</code></pre>' );
+				} );
+			} );
+		} );
+	} );
+} );

+ 109 - 0
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/lists.js

@@ -0,0 +1,109 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import MarkdownDataProcessor from '/ckeditor5/markdown-gfm/gfmdataprocessor.js';
+import { stringify } from '/tests/engine/_utils/view.js';
+
+describe( 'GFMDataProcessor', () => {
+	let dataProcessor;
+
+	beforeEach( () => {
+		dataProcessor = new MarkdownDataProcessor();
+	} );
+
+	describe( 'lists', () => {
+		describe( 'toView', () => {
+			it( 'should process tight asterisks', () => {
+				const viewFragment = dataProcessor.toView( '*	item 1\n*	item 2\n*	item 3' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<ul><li>item 1</li><li>item 2</li><li>item 3</li></ul>' );
+			} );
+
+			it( 'should process loose asterisks', () => {
+				const viewFragment = dataProcessor.toView( '*	item 1\n\n*	item 2\n\n*	item 3' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<ul><li><p>item 1</p></li><li><p>item 2</p></li><li><p>item 3</p></li></ul>' );
+			} );
+
+			it( 'should process tight pluses', () => {
+				const viewFragment = dataProcessor.toView( '+	item 1\n+	item 2\n+	item 3' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<ul><li>item 1</li><li>item 2</li><li>item 3</li></ul>' );
+			} );
+
+			it( 'should process loose pluses', () => {
+				const viewFragment = dataProcessor.toView( '+	item 1\n\n+	item 2\n\n+	item 3' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<ul><li><p>item 1</p></li><li><p>item 2</p></li><li><p>item 3</p></li></ul>' );
+			} );
+
+			it( 'should process tight minuses', () => {
+				const viewFragment = dataProcessor.toView( '-	item 1\n-	item 2\n-	item 3' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<ul><li>item 1</li><li>item 2</li><li>item 3</li></ul>' );
+			} );
+
+			it( 'should process loose minuses', () => {
+				const viewFragment = dataProcessor.toView( '-	item 1\n\n-	item 2\n\n-	item 3' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<ul><li><p>item 1</p></li><li><p>item 2</p></li><li><p>item 3</p></li></ul>' );
+			} );
+
+			it( 'should process ordered list with tabs', () => {
+				const viewFragment = dataProcessor.toView( '1.	item 1\n2.	item 2\n3.	item 3' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<ol><li>item 1</li><li>item 2</li><li>item 3</li></ol>' );
+			} );
+
+			it( 'should process ordered list with spaces', () => {
+				const viewFragment = dataProcessor.toView( '1. item 1\n2. item 2\n3. item 3' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<ol><li>item 1</li><li>item 2</li><li>item 3</li></ol>' );
+			} );
+
+			it( 'should process loose ordered list with tabs', () => {
+				const viewFragment = dataProcessor.toView( '1.	item 1\n\n2.	item 2\n\n3.	item 3' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<ol><li><p>item 1</p></li><li><p>item 2</p></li><li><p>item 3</p></li></ol>' );
+			} );
+
+			it( 'should process loose ordered list with spaces', () => {
+				const viewFragment = dataProcessor.toView( '1. item 1\n\n2. item 2\n\n3. item 3' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<ol><li><p>item 1</p></li><li><p>item 2</p></li><li><p>item 3</p></li></ol>' );
+			} );
+
+			it( 'should process ordered list with multiple paragraphs in them', () => {
+				const viewFragment = dataProcessor.toView( '1.	Item 1, graf one.\n\n	Item 2. graf two. The quick brown fox jumped over the lazy dogs\n	back.\n	\n2.	Item 2.\n\n3.	Item 3.' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<ol><li><p>Item 1, graf one.</p><p>Item 2. graf two. The quick brown fox jumped over the lazy dogs<br></br>back.</p></li><li><p>Item 2.</p></li><li><p>Item 3.</p></li></ol>' );
+			} );
+
+			it( 'should process nested lists', () => {
+				const viewFragment = dataProcessor.toView( '*	Tab\n	*	Tab\n		*	Tab' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<ul><li>Tab<ul><li>Tab<ul><li>Tab</li></ul></li></ul></li></ul>' );
+			} );
+
+			it( 'should process nested and mixed lists', () => {
+				const viewFragment = dataProcessor.toView( '1. First\n2. Second:\n	* Fee\n	* Fie\n	* Foe\n3. Third' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<ol><li>First</li><li>Second:<ul><li>Fee</li><li>Fie</li><li>Foe</li></ul></li><li>Third</li></ol>' );
+			} );
+
+			it( 'should process nested and mixed loose lists', () => {
+				const viewFragment = dataProcessor.toView( '1. First\n\n2. Second:\n	* Fee\n	* Fie\n	* Foe\n\n3. Third' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<ol><li><p>First</p></li><li><p>Second:</p><ul><li>Fee</li><li>Fie</li><li>Foe</li></ul></li><li><p>Third</p></li></ol>' );
+			} );
+
+			it( 'should create same bullet from different list indicatiors', () => {
+				const viewFragment = dataProcessor.toView( '* test\n+ test\n- test' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<ul><li>test</li><li>test</li><li>test</li></ul>' );
+			} );
+		} );
+	} );
+} );

+ 61 - 0
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/paragraphs.js

@@ -0,0 +1,61 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import MarkdownDataProcessor from '/ckeditor5/markdown-gfm/gfmdataprocessor.js';
+import { stringify } from '/tests/engine/_utils/view.js';
+
+describe( 'GFMDataProcessor', () => {
+	let dataProcessor;
+
+	beforeEach( () => {
+		dataProcessor = new MarkdownDataProcessor();
+	} );
+
+	describe( 'paragraphs', () => {
+		describe( 'toView', () => {
+			it( 'single line', () => {
+				const viewFragment = dataProcessor.toView( 'single line paragraph' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<p>single line paragraph</p>' );
+			} );
+
+			it( 'multiline', () => {
+				const viewFragment = dataProcessor.toView( 'first\n    second\n   third' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<p>first<br></br>    second<br></br>   third</p>' );
+			} );
+
+			it( 'with header after #1', () => {
+				const viewFragment = dataProcessor.toView( 'single line\n# header' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<p>single line</p><h1 id="header">header</h1>' );
+			} );
+
+			it( 'with header after #2', () => {
+				const viewFragment = dataProcessor.toView( 'single line\nheader\n===' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<p>single line</p><h1 id="header">header</h1>' );
+			} );
+
+			it( 'with blockquote after', () => {
+				const viewFragment = dataProcessor.toView( 'single line\n> quote' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<p>single line</p><blockquote><p>quote</p></blockquote>' );
+			} );
+
+			it( 'with list after', () => {
+				const viewFragment = dataProcessor.toView( 'single line\n* item' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<p>single line</p><ul><li>item</li></ul>' );
+			} );
+
+			it( 'with div element after', () => {
+				const viewFragment = dataProcessor.toView( 'single line\n<div>div element</div>' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<p>single line</p><div>div element</div>' );
+			} );
+		} );
+	} );
+} );

+ 61 - 0
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/strong_and_emphasis.js

@@ -0,0 +1,61 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import MarkdownDataProcessor from '/ckeditor5/markdown-gfm/gfmdataprocessor.js';
+import { stringify } from '/tests/engine/_utils/view.js';
+
+describe( 'GFMDataProcessor', () => {
+	let dataProcessor;
+
+	beforeEach( () => {
+		dataProcessor = new MarkdownDataProcessor();
+	} );
+
+	describe( 'strong and emphasis', () => {
+		describe( 'toView', () => {
+			it( 'should process strong', () => {
+				const viewFragment = dataProcessor.toView( '**this is strong** and __this too__' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<p><strong>this is strong</strong> and <strong>this too</strong></p>' );
+			} );
+
+			it( 'should process emphasis', () => {
+				const viewFragment = dataProcessor.toView( '*this is emphasis* and _this too_' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<p><em>this is emphasis</em> and <em>this too</em></p>' );
+			} );
+
+			it( 'should process strong and emphasis together #1', () => {
+				const viewFragment = dataProcessor.toView( '***This is strong and em.***' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<p><strong><em>This is strong and em.</em></strong></p>' );
+			} );
+
+			it( 'should process strong and emphasis together #2', () => {
+				const viewFragment = dataProcessor.toView( 'Single ***word*** is strong and em.' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<p>Single <strong><em>word</em></strong> is strong and em.</p>' );
+			} );
+
+			it( 'should process strong and emphasis together #3', () => {
+				const viewFragment = dataProcessor.toView( '___This is strong and em.___' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<p><strong><em>This is strong and em.</em></strong></p>' );
+			} );
+
+			it( 'should process strong and emphasis together #4', () => {
+				const viewFragment = dataProcessor.toView( 'Single ___word___ is strong and em.' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<p>Single <strong><em>word</em></strong> is strong and em.</p>' );
+			} );
+
+			it( 'should not process emphasis inside words', () => {
+				const viewFragment = dataProcessor.toView( 'This should_not_be_emp.' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<p>This should_not_be_emp.</p>' );
+			} );
+		} );
+	} );
+} );

+ 49 - 0
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/tabs.js

@@ -0,0 +1,49 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import MarkdownDataProcessor from '/ckeditor5/markdown-gfm/gfmdataprocessor.js';
+import { stringify } from '/tests/engine/_utils/view.js';
+
+describe( 'GFMDataProcessor', () => {
+	let dataProcessor;
+
+	beforeEach( () => {
+		dataProcessor = new MarkdownDataProcessor();
+	} );
+
+	describe( 'tabs', () => {
+		describe( 'toView', () => {
+			it( 'should process list item with tabs', () => {
+				const viewFragment = dataProcessor.toView( '+	this is a list item indented with tabs' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<ul><li>this is a list item indented with tabs</li></ul>' );
+			} );
+
+			it( 'should process list item with spaces', () => {
+				const viewFragment = dataProcessor.toView( '+   this is a list item indented with spaces' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<ul><li>this is a list item indented with spaces</li></ul>' );
+			} );
+
+			it( 'should process code block indented by tab', () => {
+				const viewFragment = dataProcessor.toView( '	this code block is indented by one tab' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<pre><code>this code block is indented by one tab</code></pre>' );
+			} );
+
+			it( 'should process code block indented by two tabs', () => {
+				const viewFragment = dataProcessor.toView( '		this code block is indented by two tabs' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<pre><code>    this code block is indented by two tabs</code></pre>' );
+			} );
+
+			it( 'should process list items indented with tabs - code block', () => {
+				const viewFragment = dataProcessor.toView( '	+	list item\n		next line' );
+
+				expect( stringify( viewFragment ) ).to.equal( '<pre><code>+    list item\n    next line</code></pre>' );
+			} );
+		} );
+	} );
+} );