/* bender-tags: editor */
/* bender-ckeditor-plugins: toolbar,wysiwygarea,entities,clipboard,pastetext */
/* bender-include: _helpers/pasting.js */
/* global assertPasteEvent, simulatePasteCommand */

/*
 * TOP TIP for all tests - DO NOT use editor.setData() or editor.editable().setHtml()
 * without setting selection. This causes on some IEs exceptions in CKEDITOR.dom.range
 * (stack trace starts from clipboard/plugin.js: sel.selectBookmarks( bms ))
 */

( function() {
	'use strict';

	bender.editor = {
		config: {
			clipboard_defaultContentType: 'text',
			allowedContent: true,
			// Disable pasteFilter on Webkits (pasteFilter defaults semantic-text on Webkits).
			pasteFilter: null
		}
	};

	function testEditor( tc, config, callback ) {
		// Disable pasteFilter on Webkits (pasteFilter defaults semantic-text on Webkits).
		config.pasteFilter = null;

		var ta = document.createElement( 'textarea' );
		document.body.appendChild( ta );

		var editor = CKEDITOR.replace( ta, config );

		editor.on( 'instanceReady', function() {
			tc.resume( function() {
				callback( editor );
			} );
		} );

		tc.wait();
	}

	function assertAfterPasteContent( tc, html, callback ) {
		tc.editor.on( 'afterPaste', function( evt ) {
			evt.removeListener();
			tc.resume( function() {
				assert.areSame( html, tc.editor.getData() );
				callback && callback();
			} );
		} );
	}

	var trustyEdge = CKEDITOR.env.edge && CKEDITOR.env.version >= 16;

	bender.test( {
		setUp: function() {
			// Force result data un-formatted.
			this.editor.dataProcessor.writer._.rules = {};
			this.editor.focus();
		},

		/**
		 * Remove all editor's paste listeners that were set by corresponding
		 * method tc.on()
		 */
		tearDown: function() {
			var editor = this.editor,
				name;

			CKEDITOR.plugins.clipboard.copyCutData = undefined;

			for ( name in { paste: 1, beforePaste: 1, afterPaste: 1 } )
				this[ name + 'Callback' ] && editor.removeListener( name, this[ name + 'Callback' ] );

			this.pasteCallback = this.beforePasteCallback = this.afterPasteCallback = null;
		},

		/**
		 * Add listener to the editor instance, so it'll be possible to remove it later
		 * by tearDown method.
		 */
		on: function( name, callback, priority ) {
			this.editor.on( name, callback, null, null, priority );
			this[ name + 'Callback' ] = callback;
		},

		'paste text': function() {
			var editor = this.editor;

			bender.tools.setHtmlWithSelection( editor, '<p>foo^bar</p>' );
			bender.tools.emulatePaste( this.editor, '<p>bam</p>' );

			assertAfterPasteContent( this, '<p>foobambar</p>' );
			this.wait();
		},

		'paste html': function() {
			var tc = this,
				editor = this.editor;

			bender.tools.setHtmlWithSelection( editor, '<p>foo^bar</p>' );
			bender.tools.emulatePaste( editor, 'abc<b>def</b>' );

			assertAfterPasteContent( tc, '<p>fooabc<b>def</b>bar</p>' );
			tc.wait();
		},

		'paste into selection': function() {
			var tc = this,
				editor = this.editor;

			bender.tools.setHtmlWithSelection( editor, '<p>fo[ob]ar</p>' );
			bender.tools.emulatePaste( editor, '<p>abc</p>' );

			assertAfterPasteContent( tc, '<p>foabcar</p>' );
			tc.wait();
		},

		'paste events': function() {
			var tc = this,
				editor = this.editor,
				order = [];

			editor.on( 'beforePaste', function( evt ) {
				evt.removeListener();
				order.push( 'a-' + evt.data.type );
			} );

			editor.on( 'paste', function( evt ) {
				var data = evt.data;
				evt.removeListener();
				order.push( 'b-' + data.type + '-' + ( data.dataValue && data.dataValue.toLowerCase() ) );
			} );

			editor.on( 'afterPaste', function( evt ) {
				evt.removeListener();
				tc.resume( function() {
					assert.areEqual( 'a-auto', order[ 0 ], 'proper order and data for beforePaste' );
					assert.areEqual( 'b-html-<b>foo</b>', order[ 1 ], 'proper order and data for paste' );
				} );
			} );

			bender.tools.emulatePaste( editor, '<b>foo</b>' );
			this.wait();
		},

		'cancel beforePaste': function() {
			var tc = this,
				editor = this.editor,
				flag = null;

			tc.on( 'beforePaste', function( evt ) {
				evt.cancel();
				flag = true;
			} );

			tc.on( 'paste', function() {
				flag = false;
			} );

			tc.on( 'afterPaste', function() {
				flag = false;
			} );

			bender.tools.setHtmlWithSelection( editor, '^' );
			bender.tools.emulatePaste( editor, 'foo' );

			// Let paste and afterPaste be fired (if there's a bug somewhere).
			tc.wait( function() {
				assert.isTrue( flag, 'canceling beforePaste stops execution' );
				assert.areEqual( '', editor.getData() );
			}, 50 );
		},

		'cancel paste': function() {
			var tc = this,
				editor = this.editor,
				flag = null;

			tc.on( 'paste', function( evt ) {
				evt.cancel();
				flag = true;
			} );

			tc.on( 'afterPaste', function() {
				flag = false;
			} );

			bender.tools.setHtmlWithSelection( editor, '^' );
			bender.tools.emulatePaste( editor, 'foo' );

			// Let afterPaste be fired (if there's a bug somewhere).
			tc.wait( function() {
				assert.areEqual( '', editor.getData() );
				assert.isTrue( flag, 'canceling paste stops execution' );
			}, 50 );
		},

		'modify pasted content': function() {
			var tc = this,
				editor = this.editor;

			editor.on( 'paste', function( evt ) {
				evt.removeListener();
				evt.data.dataValue = '<b>' + evt.data.dataValue + '</b>';
			} );

			bender.tools.setHtmlWithSelection( editor, '^' );
			bender.tools.emulatePaste( editor, '<u>f</u>oo' );

			assertAfterPasteContent( tc, '<p><b><u>f</u>oo</b></p>' );
			tc.wait();
		},

		'editor#paste command': function() {
			var editor = this.editor,
				order = [];

			function onPaste( evt ) {
				order.push( 'b-' + evt.data.type + '-' + evt.data.dataValue );
				wait();
			}

			editor.on( 'beforePaste', function( evt ) {
				evt.removeListener();
				order.push( 'a-' + evt.data.type );
			} );

			editor.on( 'afterPaste', function( evt ) {
				resume( function() {
					evt.removeListener();
					assert.areEqual( 'a-auto', order[ 0 ], 'proper order and data for beforePaste' );
					assert.areEqual( 'b-html-<b>foo</b>bar', order[ 1 ], 'proper order and data for paste' );
				} );
			} );

			bender.tools.setHtmlWithSelection( editor, '<p>[abc]</p>' );
			simulatePasteCommand( editor, { name: 'paste' }, { dataValue: '<b>foo</b>bar' }, onPaste );
		},

		'pasting empty string with editor#paste command': function() {
			var tc = this,
				editor = this.editor,
				wasPaste = false,
				wasAfterPaste = false;

			function callback() {
				assert.isFalse( wasPaste, 'paste callback shouldn\'t be called' );
				assert.isFalse( wasAfterPaste, 'afterPaste callback shouldn\'t be called' );
				assert.areEqual( editor.getData(), '<p>abc</p>' );
			}

			editor.once( 'paste', function() {
				wasPaste = true;
			} );
			editor.once( 'afterPaste', function() {
				wasAfterPaste = true;
			} );

			bender.tools.setHtmlWithSelection( editor, '<p>[abc]</p>' );

			if ( CKEDITOR.env.ie && !CKEDITOR.env.edge ) {
				// Calling editor.execCommand( 'paste' ) directly in IE triggers the security dialog.
				// The simulatePasteCommand wrapper should be used.
				simulatePasteCommand( editor, { name: 'paste' }, { dataValue: '' }, callback, 50 );
			} else {
				editor.execCommand( 'paste', '' );
				tc.wait( callback, 50 );
			}
		},

		'pasting empty string (native version)': function() {
			var tc = this,
				editor = this.editor,
				wasPaste = false,
				wasAfterPaste = false;

			editor.once( 'paste', function() {
				wasPaste = true;
			} );
			editor.once( 'afterPaste', function() {
				wasAfterPaste = true;
			} );

			bender.tools.setHtmlWithSelection( editor, '<p>[abc]</p>' );
			bender.tools.emulatePaste( editor, '' );
			tc.wait( function() {
				assert.isFalse( wasPaste, 'paste callback shouldn\'t be called' );
				assert.isFalse( wasAfterPaste, 'afterPaste callback shouldn\'t be called' );
				assert.areEqual( editor.getData(), '<p>abc</p>' );
			}, 50 );
		},

		'paste events - forcePasteAsPlainText': function() {
			var beforeType;

			testEditor( this, { forcePasteAsPlainText: true }, function( editor ) {
				editor.on( 'beforePaste', function( evt ) {
					evt.removeListener();
					beforeType = evt.data.type;
				} );

				function onPaste( evt ) {
					assert.areEqual( 'text', beforeType, 'beforePaste.data.type' );
					assert.areEqual( 'text', evt.data.type, 'paste.data.type' );
					assert.areEqual( '<p>foo bar</p>', evt.data.dataValue, 'paste.data.data' );
				}

				// We need to enable this command manually, because this listener is executed before event#mode
				// which refreshes commands automatically.
				editor.getCommand( 'paste' ).enable();
				simulatePasteCommand( editor, { name: 'paste' }, { dataValue: '<p><b>foo</b> bar</p>' }, onPaste );
			} );
		},

		'content type sniffing - text': function() {
			assertPasteEvent( this.editor, { dataValue: 'abc' },
				{ type: 'text' }, 'text only' );

			assertPasteEvent( this.editor, { dataValue: 'aa bb> cc' },
				{ type: 'text' }, 'text only 2' );

			assertPasteEvent( this.editor, { dataValue: 'a&nbsp;bc' },
				{ type: 'text' }, 'text only 3' );
		},

		'content type sniffing - htmlified text - Firefox & Opera': function() {
			if ( !CKEDITOR.env.gecko )
				assert.ignore();

			assertPasteEvent( this.editor, { dataValue: 'a<br>bc' },
				{ type: 'text' }, 'htmlified text' );

			assertPasteEvent( this.editor, { dataValue: '<br>' },
				{ type: 'text' }, 'htmlified text 2' );

			assertPasteEvent( this.editor, { dataValue: 'a<br />bc' },
				{ type: 'text' }, 'htmlified text 3' );

			assertPasteEvent( this.editor, { dataValue: 'a<br/>bc' },
				{ type: 'text' }, 'htmlified text 4' );

			assertPasteEvent( this.editor, { dataValue: '<p>a</p>' },
				{ type: 'html' }, 'html 1' );

			assertPasteEvent( this.editor, { dataValue: '<hr>' },
				{ type: 'html' }, 'html 2' );
		},

		'content type sniffing - htmlified text - Webkit': function() {
			if ( !CKEDITOR.env.webkit )
				assert.ignore();

			assertPasteEvent( this.editor, { dataValue: '<div>a</div>' },
				{ type: 'text' }, 'htmlified text 1' );

			assertPasteEvent( this.editor, { dataValue: '<div>a</div><div><br></div><div><br></div><div>b</div>' },
				{ type: 'text' }, 'htmlified text 2' );

			assertPasteEvent( this.editor, { dataValue: '<div><br /></div>' },
				{ type: 'text' }, 'htmlified text 3' );

			assertPasteEvent( this.editor, { dataValue: '<p>a</p>' },
				{ type: 'html' }, 'html 1' );

			assertPasteEvent( this.editor, { dataValue: '<hr>' },
				{ type: 'html' }, 'html 2' );

			assertPasteEvent( this.editor, { dataValue: '<br>' },
				{ type: 'html' }, 'html 3' );

			assertPasteEvent( this.editor, { dataValue: '<div>a<br>b</div>' },
				{ type: 'html' }, 'html 4' );

			assertPasteEvent( this.editor, { dataValue: 'a<br />bc' },
				{ type: 'html' }, 'html 5' );

			assertPasteEvent( this.editor, { dataValue: '<div>a<div>b</div>c</div>' },
				{ type: 'html' }, 'html 6' );
		},

		'content type sniffing - htmlified text - IEs': function() {
			if ( !CKEDITOR.env.ie )
				assert.ignore();

			assertPasteEvent( this.editor, { dataValue: '<p>a</p>' },
				{ type: 'text' }, 'htmlified text 1' );

			assertPasteEvent( this.editor, { dataValue: '<p>a<br></p><p><br>b<br>c</p><P>d<BR></P>' },
				{ type: 'text' }, 'htmlified text 2' );

			assertPasteEvent( this.editor, { dataValue: 'a<br>b' },
				{ type: 'text' }, 'htmlified text 3' );

			assertPasteEvent( this.editor, { dataValue: 'a<BR />b' },
				{ type: 'text' }, 'htmlified text 4' );

			assertPasteEvent( this.editor, { dataValue: '<p>a</p>\r\n<p>a</p><p>a</p>' },
				{ type: 'text' }, 'htmlified text 5' );

			assertPasteEvent( this.editor, { dataValue: '<div>a</div>' },
				{ type: 'html' }, 'html 1' );

			assertPasteEvent( this.editor, { dataValue: '<hr>' },
				{ type: 'html' }, 'html 2' );

			assertPasteEvent( this.editor, { dataValue: '<div>a<br>b</div>' },
				{ type: 'html' }, 'html 4' );

			assertPasteEvent( this.editor, { dataValue: '<p>a<p>b</p>c</p>' },
				{ type: 'html' }, 'html 5' );

			assertPasteEvent( this.editor, { dataValue: '<p>a</p>c' },
				{ type: 'html' }, 'html 6' );

			assertPasteEvent( this.editor, { dataValue: '<br><p>a</p>' },
				{ type: 'html' }, 'html 7' );
		},

		'content type sniffing - htmlified text - other': function() {
			if ( CKEDITOR.env.gecko || CKEDITOR.env.webkit || CKEDITOR.env.ie )
				assert.ignore();

			assertPasteEvent( this.editor, { dataValue: 'abc', htmlified: true },
				{ type: 'html' }, 'for unrecognized browser always shout html' );
		},

		'content type sniffing - html': function() {
			assertPasteEvent( this.editor, { dataValue: 'a<b>b</b>c<br>d' },
				{ type: 'html' }, 'simple html' );

			assertPasteEvent( this.editor, { dataValue: 'a<p style="color: red">b</p>c' },
				{ type: 'html' }, 'styled html' );

			assertPasteEvent( this.editor, { dataValue: '<br style="color: red">' },
				{ type: 'html' }, 'styled html 2' );

			assertPasteEvent( this.editor, { dataValue: 'a<div><p>b</p></div>c' },
				{ type: 'html' }, 'p & div only, but not created from plain text' );

			assertPasteEvent( this.editor, { dataValue: 'a<div><div>b</div>c</div>d' },
				{ type: 'html' }, 'divs only, but not created from plain text' );

			assertPasteEvent( this.editor, { dataValue: 'A<div>B<br>C</div>D' },
				{ type: 'html', dataValue: 'A<div>B<br>C</div>D' }, 'case 1b' );

			assertPasteEvent( this.editor, { dataValue: '<p>A<br>B</p><p>C</p><div>D</div>' },
				{ type: 'html', dataValue: '<p>A<br>B</p><p>C</p><div>D</div>' }, 'case 2a' );
			assertPasteEvent( this.editor, { dataValue: '<div>A</div><p>B<br>C</p><p>D</p>' },
				{ type: 'html', dataValue: '<div>A</div><p>B<br>C</p><p>D</p>' }, 'case 2b' );

			assertPasteEvent( this.editor, { dataValue: 'A<div>B</div>C<div>D</div>E' },
				{ type: 'html', dataValue: 'A<div>B</div>C<div>D</div>E' }, 'case 3a' );
		},

		'content type sniffing - don\'t change type': function() {
			assertPasteEvent( this.editor, { dataValue: 'a<b>b</b>c<hr>d', type: 'text' },
				{ type: 'text' }, 'keep text' );

			assertPasteEvent( this.editor, { dataValue: 'abc', type: 'html' },
				{ type: 'html' }, 'keep html' );
		},

		'content type sniffing - default config.clipboard_defaultContentType': function() {
			testEditor( this, {}, function( editor ) {
				// Use htmlified:true to prevent htmlification for plain text (which needs editor setup).
				assertPasteEvent(
					editor,
					{ dataValue: 'abc', htmlified: true },
					{ type: 'html' }, 'case 1a'
				);

				if ( CKEDITOR.env.webkit ) {
					assertPasteEvent( editor, { dataValue: '<div>abc</div>' },
						{ type: 'html' }, 'case 2a' );
					assertPasteEvent( editor, { dataValue: '<div>abc</div>', type: 'text' },
						{ type: 'text' }, 'case 2b' );
				} else {
					assertPasteEvent( editor, { dataValue: 'ab<br>cd' },
						{ type: 'html' }, 'case 3a' );
					assertPasteEvent( editor, { dataValue: 'ab<br>cd', type: 'text' },
						{ type: 'text' }, 'case 3b' );
				}
			} );
		},

		'htmlified text unification': function() {
			// TCs extracted from _docs/plaintext.txt.

			CKEDITOR.env.gecko && assertPasteEvent(
				this.editor,
				{
					dataValue: '1aaa<br>2bbb&nbsp;&nbsp; &nbsp;ccc<br><br>&nbsp; 3ddd<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;4eee' +
					'<br>&nbsp;&nbsp; &nbsp;5fff<br><br><br>6ggg&nbsp; hhh<br><br>'
				},
				{
					type: 'text', dataValue: '<p>1aaa<br>2bbb&nbsp;&nbsp; &nbsp;ccc</p><p>&nbsp; 3ddd<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;' +
					' &nbsp;4eee<br>&nbsp;&nbsp; &nbsp;5fff</p><p><br>6ggg&nbsp; hhh</p><p></p>'
				},
				'htmlified text - fx'
			);
			CKEDITOR.env.ie && assertPasteEvent(
				this.editor,
				{ dataValue: '<P>1aaa<BR>2bbb&nbsp;ccc</P>\r\n<P>&nbsp; 3ddd<BR>&nbsp;&nbsp;4eee<BR>&nbsp;5fff</P>\r\n<P><BR>6ggg&nbsp; hhh<BR></P>' },
				{ type: 'text', dataValue: '<p>1aaa<br>2bbb&nbsp;ccc</p><p>&nbsp; 3ddd<br>&nbsp;&nbsp;4eee<br>&nbsp;5fff</p><p><br>6ggg&nbsp; hhh<br></p>' },
				'htmlified text - ie'
			);
			CKEDITOR.env.webkit && assertPasteEvent(
				this.editor,
				{
					dataValue: '<div>1aaa</div><div>2bbb<span class="Apple-tab-span" style="white-space:pre">	</span>ccc</div><div><br></div>' +
					'<div>&nbsp; 3ddd</div><div><span class="Apple-tab-span" style="white-space:pre">\t </span>4eee</div>' +
					'<div><span class="Apple-tab-span" style="white-space:pre"> </span>5fff</div><div><br></div><div><br></div><div>6ggg &nbsp;hhh</div><div><br></div>'
				},
				{
					type: 'text', dataValue: '<p>1aaa<br>2bbb&nbsp;&nbsp; &nbsp;ccc</p><p>&nbsp; 3ddd<br>&nbsp;&nbsp; &nbsp; 4eee<br> 5fff</p><p><br>6ggg &nbsp;hhh</p><br data-cke-eol="1">'
				},
				'htmlified text - webkit'
			);
		},

		'htmlified text unification 2': function() {
			// aa\s
			// \s\s\s
			//
			// \t
			// bbb
			CKEDITOR.env.gecko && assertPasteEvent( this.editor,
				{ dataValue: 'aa <br>&nbsp; &nbsp;<br><br>&nbsp;&nbsp; &nbsp;<br>bbb' },
				{ type: 'text', dataValue: '<p>aa <br>&nbsp; &nbsp;</p><p>&nbsp;&nbsp; &nbsp;<br>bbb</p>' },
				'htmlified text 2 - fx' );
			CKEDITOR.env.ie && assertPasteEvent( this.editor,
				{ dataValue: '<P>aa <BR>&nbsp;&nbsp; </P>\r\n<P>&nbsp;<BR>bbb</P>' },
				{ type: 'text', dataValue: '<p>aa <br>&nbsp;&nbsp; </p><p>&nbsp;<br>bbb</p>' },
				'htmlified text 2 - ie' );
			CKEDITOR.env.webkit && assertPasteEvent( this.editor,
				{ dataValue: '<div>aa&nbsp;</div><div>&nbsp; &nbsp;</div><div><br></div><div><span class="Apple-tab-span" style="white-space:pre">	</span></div><div>bbb</div>' },
				{ type: 'text', dataValue: '<p>aa&nbsp;<br>&nbsp; &nbsp;</p><p>&nbsp;&nbsp; &nbsp;<br>bbb</p>' },
				'htmlified text 2 - webkit' );
		},

		'htmlified text unification 3 - other cases': function() {
			var data = CKEDITOR.env.webkit ? '<div>a&nbsp;b</div>' : 'a&nbsp;b<br>c';
			var result = CKEDITOR.env.webkit ? 'a&nbsp;b' : 'a&nbsp;b<br>c';

			assertPasteEvent( this.editor, { dataValue: data },
				{ type: 'text', dataValue: result }, 'do not encode twice - treat as htmlifiedtext' );

			assertPasteEvent( this.editor, { dataValue: data, type: 'text' },
				{ type: 'text', dataValue: result }, 'do not encode twice - treat as htmlifiedtext 2' );

			if ( CKEDITOR.env.ie ) {
				assertPasteEvent( this.editor, { dataValue: '<p>B<br>C</p>' },
					{ type: 'text', dataValue: '<p>B<br>C</p>' }, 'ie 1' );
				assertPasteEvent( this.editor, { dataValue: '<p>A<br></p>' },
					{ type: 'text', dataValue: '<p>A<br></p>' }, 'ie 2' );
				assertPasteEvent( this.editor, { dataValue: '<p>A<br></p><p>B<br></p>' },
					{ type: 'text', dataValue: '<p>A<br></p><p>B<br></p>' }, 'ie 3' );
				assertPasteEvent( this.editor, { dataValue: '<p>&nbsp;</p><p>aa</p><p>bb</p>' },
					{ type: 'text', dataValue: '<p>&nbsp;</p><p>aa</p><p>bb</p>' }, 'ie 4' );
				assertPasteEvent( this.editor, { dataValue: '<br>' },
					{ type: 'text', dataValue: '<br>' }, 'ie 5a' );
				assertPasteEvent( this.editor, { dataValue: '<br>aa' },
					{ type: 'text', dataValue: '<br>aa' }, 'ie 5b' );

				// TODO IE produces weird content for cases with more new lines.
				// Dive into this some day.
			}

			else if ( CKEDITOR.env.webkit ) {
				assertPasteEvent( this.editor,
					{ dataValue: '<div>aa</div><div>bb</div>' },
					{ type: 'text', dataValue: 'aa<br>bb' },
					'htmlified text - webkit' );

				assertPasteEvent( this.editor,
					{ dataValue: '<div>aa</div><div>bb</div><div>cc</div>' },
					{ type: 'text', dataValue: 'aa<br>bb<br>cc' },
					'htmlified text 2 - webkit' );

				assertPasteEvent( this.editor,
					{ dataValue: '<div>aa</div><div><br></div><div>cc</div>' },
					{ type: 'text', dataValue: '<p>aa</p><p>cc</p>' },
					'htmlified text 3 - webkit' );

				assertPasteEvent( this.editor,
					{ dataValue: '<div>aa</div><div><br></div><div>cc</div><div>dd</div>' },
					{ type: 'text', dataValue: '<p>aa</p><p>cc<br>dd</p>' },
					'htmlified text 4 - webkit' );

				assertPasteEvent( this.editor,
					{ dataValue: '<div>A</div><div><br></div><div>B</div><div><br></div>' },
					{ type: 'text', dataValue: '<p>A</p><p>B</p><br data-cke-eol="1">' },
					'htmlified text 5 - webkit' );

				assertPasteEvent( this.editor,
					{ dataValue: '<div>A</div><div><br></div><div><br></div><div>B</div>' },
					{ type: 'text', dataValue: '<p>A</p><p><br>B</p>' },
					'htmlified text 6 - webkit' );

				assertPasteEvent( this.editor,
					{ dataValue: '<div>A</div><div><br></div><div><br></div><div><br></div><div>B</div>' },
					{ type: 'text', dataValue: '<p>A</p><p></p><p>B</p>' },
					'htmlified text 7 - webkit' );

				assertPasteEvent( this.editor,
					{ dataValue: '<div><br></div><div>A</div>' },
					{ type: 'text', dataValue: '<br>A' },
					'htmlified text 8 - webkit' );

				assertPasteEvent( this.editor,
					{ dataValue: '<div><br></div><div><br></div><div>A</div>' },
					{ type: 'text', dataValue: '<p></p><p>A</p>' },
					'htmlified text 9 - webkit' );

				assertPasteEvent( this.editor,
					{ dataValue: '<div><br></div><div><br></div><div><br></div><div>A</div>' },
					{ type: 'text', dataValue: '<p></p><p><br>A</p>' },
					'htmlified text 10 - webkit' );
/*
				assertPasteEvent( this.editor,
					{ dataValue: '<div><br></div>' },
					{ type: 'text', dataValue: '<br>' },
					'htmlified text 11 - webkit' );

				assertPasteEvent( this.editor,
					{ dataValue: '<div><br></div><div><br></div>' },
					{ type: 'text', dataValue: '<p></p>' },
					'htmlified text 12 - webkit' );

				assertPasteEvent( this.editor,
					{ dataValue: '<div><br></div><div><br></div><div><br></div>' },
					{ type: 'text', dataValue: '<p><br></p>' },
					'htmlified text 13 - webkit' );
*/
				assertPasteEvent( this.editor,
					{ dataValue: '<div><br></div><div>aa</div>' },
					{ type: 'text', dataValue: '<br>aa' },
					'htmlified text 14 - webkit' );

				assertPasteEvent( this.editor,
					{ dataValue: '<div><br></div><div><br></div><div>aa</div>' },
					{ type: 'text', dataValue: '<p></p><p>aa</p>' },
					'htmlified text 15 - webkit' );

				assertPasteEvent( this.editor,
					{ dataValue: '<div><br></div><div><br></div><div><br></div><div>aa</div>' },
					{ type: 'text', dataValue: '<p></p><p><br>aa</p>' },
					'htmlified text 16 - webkit' );
			}
			else if ( CKEDITOR.env.gecko ) {
				// Fx is stupid - for one plain text line break it generates two brs.
				// For two line breaks - two brs as well.
				assertPasteEvent( this.editor, { dataValue: '<br><br>' },
					{ type: 'text', dataValue: '<br>' }, 'fx 1a' );
				//assertPasteEvent( this.editor, { dataValue: '<br><br><br>' },
				//	{ type: 'text', dataValue: '<p><br></p>' }, 'fx 1b' );

				assertPasteEvent( this.editor, { dataValue: '<br>aa' },
					{ type: 'text', dataValue: '<br>aa' }, 'fx 2a' );
				assertPasteEvent( this.editor, { dataValue: '<br><br>aa' },
					{ type: 'text', dataValue: '<p></p><p>aa</p>' }, 'fx 2a' );
				assertPasteEvent( this.editor, { dataValue: '<br><br><br>aa' },
					{ type: 'text', dataValue: '<p></p><p><br>aa</p>' }, 'fx 2b' );
			}
		},

		'htmlified text unification 4 - XHTML syntax': function() {
			if ( CKEDITOR.env.gecko )
				assertPasteEvent( this.editor,
					{ dataValue: 'aa<br /><br/>bb<br />cc' },
					{ type: 'text', dataValue: '<p>aa</p><p>bb<br>cc</p>' },
					'htmlified text - fx' );
			else if ( CKEDITOR.env.ie )
				assertPasteEvent( this.editor,
					{ dataValue: '<p>aa<br />bb<BR/>cc</p>' },
					{ type: 'text', dataValue: '<p>aa<br>bb<br>cc</p>' },
					'htmlified text - ie' );
			else if ( CKEDITOR.env.webkit )
				assertPasteEvent( this.editor,
					{ dataValue: '<div>aa</div><div><br /></div><div>bb</div>' },
					{ type: 'text', dataValue: '<p>aa</p><p>bb</p>' },
					'htmlified text - webkit' );
			else
				assert.isTrue( true );
		},

		'htmlified text unification 5 - enter mode br': function() {
			testEditor( this, {
				enterMode: CKEDITOR.ENTER_BR,
				clipboard_defaultContentType: 'text'
			},
			function( editor ) {
				if ( CKEDITOR.env.gecko ) {
					assertPasteEvent( editor,
						{ dataValue: 'aa<br>bb' }, { type: 'text', dataValue: 'aa<br>bb' },
						'htmlified text 1 - fx' );
					assertPasteEvent( editor,
						{ dataValue: 'aa<br><br>bb' }, { type: 'text', dataValue: 'aa<br><br>bb' },
						'htmlified text 2 - fx' );
					assertPasteEvent( editor,
						{ dataValue: 'aa<br><br><br>bb' }, { type: 'text', dataValue: 'aa<br><br><br>bb' },
						'htmlified text 3 - fx' );
					assertPasteEvent( editor,
						{ dataValue: '<br>aa<br><br>bb<br>cc<br>' }, { type: 'text', dataValue: '<br>aa<br><br>bb<br>cc<br>' },
						'htmlified text 4 - fx' );
				}
				else if ( CKEDITOR.env.ie ) {
					assertPasteEvent( editor,
						{ dataValue: '<p>aa</p>' }, { type: 'text', dataValue: 'aa' },
						'htmlified text 1 - ie' );
					assertPasteEvent( editor,
						{ dataValue: '<p>aa</p><p>bb</p>' }, { type: 'text', dataValue: 'aa<br><br>bb' },
						'htmlified text 2 - ie' );
					assertPasteEvent( editor,
						{ dataValue: '<p>aa</p><p><br>bb</p>' }, { type: 'text', dataValue: 'aa<br><br><br>bb' },
						'htmlified text 3 - ie' );
					// TODO Since we are blindly removing spaces - we're failing in these cases.
					assertPasteEvent( editor,
						{ dataValue: '<p>aa</p><p>&nbsp;</p><p>bb</p>' },
						{ type: 'text', dataValue: 'aa<br><br>&nbsp;<br><br>bb' },
						'htmlified text 4 - ie' );
					assertPasteEvent( editor,
						{ dataValue: '<p>&nbsp;</p><p>aa</p><p>bb</p>' },
						{ type: 'text', dataValue: '&nbsp;<br><br>aa<br><br>bb' },
						'htmlified text 5 - ie' );
					assertPasteEvent( editor,
						{ dataValue: '<p><br>aa</p><p>bb</p>' },
						{ type: 'text', dataValue: '<br>aa<br><br>bb' },
						'htmlified text 6 - ie' );
				}
				else if ( CKEDITOR.env.webkit ) {
					assertPasteEvent( editor,
						{ dataValue: '<div>aa</div>' }, { type: 'text', dataValue: 'aa' },
						'htmlified text 1 - webkit' );
					assertPasteEvent( editor,
						{ dataValue: '<div>aa</div><div>bb</div>' }, { type: 'text', dataValue: 'aa<br>bb' },
						'htmlified text 2 - webkit' );
					assertPasteEvent( editor,
						{ dataValue: '<div>aa</div><div>bb</div><div>cc</div>' },
						{ type: 'text', dataValue: 'aa<br>bb<br>cc' },
						'htmlified text 3 - webkit' );
					assertPasteEvent( editor,
						{ dataValue: '<div>aa</div><div><br></div><div>bb</div>' },
						{ type: 'text', dataValue: 'aa<br><br>bb' },
						'htmlified text 4 - webkit' );
					assertPasteEvent( editor,
						{ dataValue: '<div>aa</div><div><br></div><div><br></div><div>bb</div>' },
						{ type: 'text', dataValue: 'aa<br><br><br>bb' },
						'htmlified text 5 - webkit' );
					assertPasteEvent( editor,
						{ dataValue: '<div>aa</div><div><br></div><div><br></div><div><br></div><div>bb</div>' },
						{ type: 'text', dataValue: 'aa<br><br><br><br>bb' },
						'htmlified text 6 - webkit' );
					assertPasteEvent( editor,
						{ dataValue: '<div><br></div><div>aa</div>' }, { type: 'text', dataValue: '<br>aa' },
						'htmlified text 7 - webkit' );
					assertPasteEvent( editor,
						{ dataValue: '<div><br></div><div>aa</div><div><br></div><div>bb</div>' },
						{ type: 'text', dataValue: '<br>aa<br><br>bb' },
						'htmlified text 8 - webkit' );
					assertPasteEvent( editor,
						{ dataValue: '<div><br></div><div><br></div><div>aa</div><div><br></div><div>bb</div>' },
						{ type: 'text', dataValue: '<br><br>aa<br><br>bb' },
						'htmlified text 9 - webkit' );
					assertPasteEvent( editor,
						{ dataValue: '<div>aa</div><div><br></div><div>bb</div><div><br></div>' },
						{ type: 'text', dataValue: 'aa<br><br>bb<br data-cke-eol="1">' },
						'htmlified text 10 - webkit' );
				}
			} );
		},

		'htmlified text unification 6 - enter mode div': function() {
			testEditor( this, {
				enterMode: CKEDITOR.ENTER_DIV,
				clipboard_defaultContentType: 'text'
			},
			function( editor ) {
				if ( CKEDITOR.env.gecko ) {
					assertPasteEvent( editor,
						{ dataValue: 'aa<br>bb' }, { type: 'text', dataValue: 'aa<br>bb' },
						'htmlified text 1 - fx' );
					assertPasteEvent( editor,
						{ dataValue: 'aa<br>bb<br><br>cc' }, { type: 'text', dataValue: '<div>aa<br>bb</div><div>cc</div>' },
						'htmlified text 2 - fx' );
				}
				else if ( CKEDITOR.env.ie ) {
					assertPasteEvent( editor,
						{ dataValue: 'aa<br>bb' }, { type: 'text', dataValue: 'aa<br>bb' },
						'htmlified text 1 - ie' );
					assertPasteEvent( editor,
						{ dataValue: '<p>aa<br>bb</p><p>cc</p>' },
						{ type: 'text', dataValue: '<div>aa<br>bb</div><div>cc</div>' },
						'htmlified text 2 - ie' );
				}
				else if ( CKEDITOR.env.webkit ) {
					assertPasteEvent( editor,
						{ dataValue: '<div>aa</div><div>bb</div>' }, { type: 'text', dataValue: 'aa<br>bb' },
						'htmlified text 1 - webkit' );
					// TODO interesting... better to keep Webkit's original, or to transform like for the
					// other browsers.
					assertPasteEvent( editor,
						{ dataValue: '<div>aa</div><div>bb</div><div><br></div><div>cc</div>' },
						{ type: 'text', dataValue: '<div>aa<br>bb</div><div>cc</div>' },
						'htmlified text 2 - webkit' );
				}
			} );
		},

		// (#1321)
		'htmlified text unification 7 - IDEOGRAPHIC space': function() {
			assertPasteEvent( this.editor,
				{ dataValue: 'a\u3000a\u3000\u3000' },
				{ type: 'text', dataValue: 'a\u3000a\u3000\u3000' },
				'htmlified text - IDEOGRAPHIC space' );
		},

		'html textification <p class="test" style="color:red">a<br style="display:none">b</p>': function() {
			assertPasteEvent( this.editor, {
					type: 'text',
					dataValue: '<p class="test" style="color:red">a<br style="display:none">b</p>'
				}, { type: 'text', dataValue: '<p>a<br />b</p>' }, 'strip styles' );
		},

		'html textification a<i>b</i><p>c<b>d</b>e<br>f</p>g': function() {
			assertPasteEvent( this.editor,
				{ type: 'text', dataValue: 'a<i>b</i><p>c<b>d</b>e<br>f</p>g' },
				{ type: 'text', dataValue: 'ab<p>cde<br />f</p>g' },
				'strip inline elements' );
		},

		'html textification <ul> <li>A1</li> <li>A2 <ol> <li>B1</li> <li>B2</li> </ol></li> </ul> <ol> <li>C1</li> </ol>': function() {
			assertPasteEvent( this.editor,
				{ type: 'text', dataValue: '<ul> <li>A1</li> <li>A2 <ol> <li>B1</li> <li>B2</li> </ol></li> </ul> <ol> <li>C1</li> </ol>' },
				{ type: 'text', dataValue: '<p>A1</p><p>A2</p><p>B1</p><p>B2</p><p>C1</p>' },
				'lists' );
		},

		'html textification <dl> <dt>AT</dt> <dd>AD <dl> <dt>BT</dt> <dd>BD1</dd><dd>BD2</dd> </dl></dd> </dl>': function() {

			assertPasteEvent( this.editor,
				{ type: 'text', dataValue: '<dl> <dt>AT</dt> <dd>AD <dl> <dt>BT</dt> <dd>BD1</dd><dd>BD2</dd> </dl></dd> </dl>' },
				{ type: 'text', dataValue: '<p>AT</p><p>AD</p><p>BT</p><p>BD1</p><p>BD2</p>' },
				'def lists' );
		},

		'html textification a <div>b</div> <div title="1">c</div> d <div>e</div>': function() {

			// Without attrib this will be handled as normal htmlified text.
			assertPasteEvent( this.editor,
				{ type: 'text', dataValue: 'a <div>b</div> <div title="1">c</div> d <div>e</div>' },
				{ type: 'text', dataValue: 'a<p>b</p><p>c</p> d<p>e</p>' },
				'divs' );
		},

		'html textification <div> <p>a</p> b</div> <div>c <ul> <li>d</li> <li>e</li> </ul></div>': function() {

			assertPasteEvent( this.editor,
				{ type: 'text', dataValue: '<div> <p>a</p> b</div> <div>c <ul> <li>d</li> <li>e</li> </ul></div>' },
				{ type: 'text', dataValue: '<p>a</p><p>b</p><p>c</p><p>d</p><p>e</p>' },
				'divs 2' );
		},

		'html textification X<table> <caption>C</caption> <tr><th>A1</th><td>A2</td></tr> <tr><td>B1</td><th>B2</th></tr> </table>X': function() {

			// TODO we should correct tbody,thead,tfoot,caption order if not done by parser.
			assertPasteEvent( this.editor,
				{ type: 'text', dataValue: 'X<table> <caption>C</caption> <tr><th>A1</th><td>A2</td></tr> <tr><td>B1</td><th>B2</th></tr> </table>X' },
				{ type: 'text', dataValue: 'X<p>C</p><p>A1A2</p><p>B1B2</p>X' },
				'tables' );
		},

		'html textification <table> <tr><td>A1</td> <td><table><tr><td>B1</td><td>B2</td></table></td></tr> <tr><td>C1</td></tr> </table>': function() {

			assertPasteEvent( this.editor,
				{ type: 'text', dataValue: '<table> <tr><td>A1</td> <td><table><tr><td>B1</td><td>B2</td></table></td></tr> <tr><td>C1</td></tr> </table>' },
				{ type: 'text', dataValue: '<p>A1</p><p>B1B2</p><p>C1</p>' },
				'tables 2' );
		},

		'html textification 2 <p title="1">a<br><br><br></p><p>b</p>': function() {
			assertPasteEvent( this.editor, {
				type: 'text', dataValue: '<p title="1">a<br><br><br></p><p>b</p>'
			}, { type: 'text', dataValue: '<p>a<br /><br /><br /></p><p>b</p>' }, 'preserve original new lines' );
		},

		'html textification 2 <div title="1"><br><br></div><p>a</p>': function() {
			assertPasteEvent( this.editor, {
				type: 'text', dataValue: '<div title="1"><br><br></div><p>a</p>'
			}, { type: 'text', dataValue: '<p><br /><br /></p><p>a</p>' }, 'preserve original new lines 2' );
		},

		'html textification 2 A<br>B<br><div title="1">C</div>D<br>E': function() {
			assertPasteEvent( this.editor, {
				type: 'text', dataValue: 'A<br>B<br><div title="1">C</div>D<br>E'
			}, { type: 'text', dataValue: 'A<br />B<br /><p>C</p>D<br />E' }, 'correct order and new line after div' );
		},

		'html textification 2 A<br>B<div title="1">C</div><br>D<br>E': function() {
			assertPasteEvent( this.editor, {
				type: 'text', dataValue: 'A<br>B<div title="1">C</div><br>D<br>E'
			}, { type: 'text', dataValue: 'A<br />B<p>C</p><br />D<br />E' }, 'correct order and new line before div' );
		},

		'html textification 2 A<div><div><p>B</p>C<ul><li>D</li></ul></div> <p>E</p></div>F': function() {
			assertPasteEvent( this.editor, {
					type: 'text',
					dataValue: 'A<div><div><p>B</p>C<ul><li>D</li></ul></div> <p>E</p></div>F'
				}, { type: 'text', dataValue: 'A<p>B</p><p>C</p><p>D</p><p>E</p>F' }, 'transparent divs' );
		},

		'html textification 3 - ticket https://dev.ckeditor.com/ticket/8834': function() {
			// Mso classes will be stripped by pastefromword filters, and we need some styling element,
			// because otherwise this will be handled as htmlified text.
			assertPasteEvent( this.editor,
				{ type: 'text', dataValue: '<p><strong>Line</strong> 1<br>Line 2</p><p>Line 3</p><p>Line 4</p>' },
				{ type: 'text', dataValue: '<p>Line 1<br />Line 2</p><p>Line 3</p><p>Line 4</p>' },
				'tt https://dev.ckeditor.com/ticket/8834' );

			assertPasteEvent( this.editor,
				{ type: 'text', dataValue: '<p><strong>Line</strong> 1<br>Line 2</p><p>Line 3</p><p>Line 4</p>' },
				{ type: 'text', dataValue: '<p>Line 1<br />Line 2</p><p>Line 3</p><p>Line 4</p>' },
				'tt https://dev.ckeditor.com/ticket/8834' );
		},

		'html textification 4': function() {
			assertPasteEvent( this.editor,
				{ type: 'text', dataValue: 'x<object>y</object>z <p>a<iframe src=".">b</iframe>c</p>' },
				{ type: 'text', dataValue: 'xyz<p>abc</p>' },
				'remove elements' );

			// input -> value? select -> current value?
			// possible problems: applet, area, fieldset, hgroup (-> h1<br>h2), select, audio, video,
		},

		'html textification 5 - complex cases #1': function() {
			assertPasteEvent( this.editor, {
				type: 'text',
				dataValue: '<section><h1>HH</h1><p>1AAAA</p><p>2AAAA</p><p>3AAAA</p></section>' + '<table><tbody><tr><td><p>1AAAA</p><p>2AAAA</p><p>3AAAA</p></td></tr></tbody></table>'
			}, {
				type: 'text',
				dataValue: '<p>HH</p><p>1AAAA</p><p>2AAAA</p><p>3AAAA</p><p>1AAAA</p><p>2AAAA</p><p>3AAAA</p>'
			}, 'complex case 1' );
		},

		'html textification 5 - complex cases #2': function() {

			assertPasteEvent( this.editor, {
					type: 'text',
					dataValue: '<section><div><p>1AAAA</p><p>2AAAA</p><p>3AAAA</p><aside><table><tr><td><p>4AAAA</p>' +
						'<ul><li>BBB</li><li>BBB</li></ul><p>5AAAA</p></td><td><p>6AAAA</p><p>7AAAA</p></td></tr></table></aside></div></section>'
				}, {
					type: 'text',
					dataValue: '<p>1AAAA</p><p>2AAAA</p><p>3AAAA</p><p>4AAAA</p><p>BBB</p><p>BBB</p><p>5AAAA</p><p>6AAAA</p><p>7AAAA</p>'
				}, 'complex case 2' );
		},

		'html textification 5 - complex cases #3': function() {

			assertPasteEvent(
				this.editor,
				{ type: 'text', dataValue: '' },
				{ type: 'text', dataValue: '' },
				'extremely complex case'
			);
		},

		'html textification 6 - tricks <p>A<img src="sth.jpg" alt="This is a title">B</p>': function() {
			assertPasteEvent( this.editor, {
				type: 'text', dataValue: '<p>A<img src="sth.jpg" alt="This is a title">B</p>'
			}, { type: 'text', dataValue: '<p>AB</p>' }, 'img alt to text' );
		},

		'html textification 6 - tricks <p>A<img src="sth.jpg" alt="sth.jpg?1=2">B</p>': function() {
			// Quite popular case in the Internet.
			assertPasteEvent( this.editor, {
				type: 'text', dataValue: '<p>A<img src="sth.jpg" alt="sth.jpg?1=2">B</p>'
			}, { type: 'text', dataValue: '<p>AB</p>' }, 'dumb alt' );
		},

		'html textification 6 - tricks <p>A<img src="sth.jpg" alt="">B</p>': function() {
			// Popular too.
			assertPasteEvent( this.editor, {
				type: 'text', dataValue: '<p>A<img src="sth.jpg" alt="">B</p>'
			}, { type: 'text', dataValue: '<p>AB</p>' }, 'dumb alt' );
		},

		'html textification 6 - tricks <p>A</p><p> </p><p>B</p><p> \t\n\n</p><p>C</p><ul></ul><p>D</p>': function() {
			assertPasteEvent( this.editor, {
					type: 'text',
					dataValue: '<p>A</p><p> </p><p>B</p><p> \t\n\n</p><p>C</p><ul></ul><p>D</p>'
				}, { type: 'text', dataValue: '<p>A</p><p>B</p><p>C</p><p>D</p>' }, 'remove empty blocks' );
		},

		'html textification 6 - tricks <p>A</p> <h1>T1</h1> <h2>T2</h2> <h3>T3</h3> <p>C</p> <h4>T4</h4> D <h5>T5</h5>': function() {
			assertPasteEvent( this.editor,
				{ type: 'text', dataValue: '<p>A</p> <h1>T1</h1> <h2>T2</h2> <h3>T3</h3> <p>C</p> <h4>T4</h4> D <h5>T5</h5>' },
				{ type: 'text', dataValue: '<p>A</p><p>T1</p><p>T2</p><p>T3</p><p>C</p><p>T4</p>D<p>T5</p>' },
				'squash adjacent headers' );
		},

		'html textification 7 - enter mode br': function() {
			testEditor( this, { enterMode: CKEDITOR.ENTER_BR },
				function( editor ) {
					assertPasteEvent( editor,
						{ type: 'text', dataValue: '<dl> <dt>AT</dt> <dd>AD <dl> <dt>BT</dt> <dd>BD1</dd><dd>BD2</dd> </dl></dd> </dl>' },
						{ type: 'text', dataValue: 'AT<br />AD<br />BT<br />BD1<br />BD2' },
						'def lists' );
				} );
		},

		'html textification 8 - enter mode div': function() {
			testEditor( this, { enterMode: CKEDITOR.ENTER_DIV },
				function( editor ) {
					assertPasteEvent( editor,
						{ type: 'text', dataValue: '<dl> <dt>AT</dt> <dd>AD <dl> <dt>BT</dt> <dd>BD1</dd><dd>BD2</dd> </dl></dd> </dl>' },
						{ type: 'text', dataValue: '<div><div>AT</div><div>AD<div><div>BT</div><div>BD1</div><div>BD2</div></div></div></div>' },
						'def lists' );
				} );
		},

		'filtering browser\'s crap': function() {
			assertPasteEvent( this.editor, { dataValue: 'a<span class="Apple-some-class" style="white-space:pre"> </span>b' },
				{ dataValue: 'a<span style="white-space:pre"> </span>b' }, 'tab-span' );

			assertPasteEvent( this.editor, { dataValue: 'a<span class="Apple-converted-space">&nbsp;</span>b' },
				{ dataValue: 'a b' }, 'space' );

			CKEDITOR.env.webkit && assertPasteEvent( this.editor,
				{ dataValue: 'a<span class="Apple-tab-span" style="white-space:pre">		</span>b' },
				{ priority: 4, dataValue: 'a&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;b' }, 'tab-span' );

			if ( CKEDITOR.env.gecko ) {
				// Firefox adds it when user pasted 'a '.
				assertPasteEvent( this.editor, { dataValue: 'a <br>', htmlified: true },
					{ dataValue: 'a ' }, 'gecko\'s bogus <br>' );
			}
		},

		// See dt/core/editable/_docs/blockselections.txt
		'fixing block content': function() {
			if ( CKEDITOR.env.ie ) {
				assertPasteEvent( this.editor, { dataValue: '&nbsp; <p>ABC</p>' },
					{ type: 'html', dataValue: '<p>ABC</p>' }, 'eol 1a' );
				assertPasteEvent( this.editor, { dataValue: '&nbsp;<p>ABC</p>' },
					{ type: 'html', dataValue: '<p>ABC</p>' }, 'eol 1b' );
				assertPasteEvent( this.editor, { dataValue: '&nbsp;\r\n<p>ABC</p>' },
					{ type: 'html', dataValue: '<p>ABC</p>' }, 'eol 1c' );
				assertPasteEvent( this.editor, { dataValue: '&nbsp; <b>ABC</b>' },
					{ type: 'html', dataValue: '&nbsp; <b>ABC</b>' }, 'eol 2' );
			}
			else if ( CKEDITOR.env.webkit ) {
				assertPasteEvent( this.editor, { dataValue: '<br class="Apple-interchange-newline">ABC' },
					{ type: 'html', dataValue: '<br data-cke-eol="1">ABC' }, 'eol 1' );

				assertPasteEvent( this.editor, { dataValue: '<br class="Apple-interchange-newline"><p>A<b>B</b>C</p>', type: 'text' },
					{ type: 'text', dataValue: '<br data-cke-eol="1"><p>ABC</p>' }, 'eol 1 - keep for forced text' );

				assertPasteEvent( this.editor, { dataValue: '<p>A<b>B</b>C</p><div><br></div>' },
					{ type: 'html', dataValue: '<p>A<b>B</b>C</p><br data-cke-eol="1">' }, 'eol 2' );

				assertPasteEvent( this.editor, { dataValue: '<p>A<b>B</b>C</p><div><br></div>', type: 'text' },
					{ type: 'text', dataValue: '<p>ABC</p><br data-cke-eol="1">' }, 'eol 2 - keep for forced text' );

				assertPasteEvent( this.editor, { dataValue: 'A<b>BC</b><div><br></div>' },
					{ type: 'html', dataValue: 'A<b>BC</b><div><br></div>' }, 'eol 3' );

				assertPasteEvent( this.editor, { dataValue: '<p><br class="Apple-interchange-newline">A</p><p>B</p><p>C</p>', type: 'text' },
					{ type: 'text', dataValue: '<br data-cke-eol="1"><p>A</p><p>B</p><p>C</p>' }, 'eol 4 - produced multiple blocks selected' );
			} else {
				assert.isTrue( true );
			}
		},

		'forcing html type': function() {
			var tc = this,
				editor = this.editor;

			tc.on( 'beforePaste', function( evt ) {
				evt.data.type = 'html';
			} );

			function onPaste( evt ) {
				assert.areEqual( 'html', evt.data.type );
			}

			simulatePasteCommand( editor, { name: 'paste' }, { dataValue: 'abc' }, onPaste );
		},

		'dataTranfer and method in paste - emulatePaste': function() {
			var editor = this.editor,
				pasteCount = 0,
				pasteMethod,
				dataTransferInPaste;

			this.on( 'paste', function( evt ) {
				pasteCount++;
				pasteMethod = evt.data.method;
				dataTransferInPaste = evt.data.dataTransfer instanceof CKEDITOR.plugins.clipboard.dataTransfer;
			} );

			bender.tools.setHtmlWithSelection( editor, '<p>foo^bar</p>' );
			bender.tools.emulatePaste( editor, '<p>bam</p>' );

			assertAfterPasteContent( this, '<p>foobambar</p>', function() {
				assert.areSame( 1, pasteCount, 'There should only one paste.' );
				assert.areSame( 'paste', pasteMethod, 'Method should be paste' );
				assert.isTrue( dataTransferInPaste, 'Paste event should contain dataTranfer' );
			} );

			this.wait();
		},

		'dataTranfer and method in paste - execCommand': function() {
			var editor = this.editor;

			function onPaste( evt ) {
				assert.isInstanceOf( CKEDITOR.plugins.clipboard.dataTransfer, evt.data.dataTransfer );
				assert.areSame( 'paste', evt.data.method, 'Method should be paste' );
			}

			bender.tools.setHtmlWithSelection( editor, '<p>foo^bar</p>' );
			simulatePasteCommand( editor, 'xxx', {}, onPaste );
		},

		'paste with HTML in clipboardData': function() {
			if ( !CKEDITOR.plugins.clipboard.isCustomCopyCutSupported ) {
				assert.ignore();
			}

			var editor = this.editor,
				editable = editor.editable(),
				pasteEventMock = bender.tools.mockPasteEvent(),
				dataValueOnPaste, htmlDataOnPaste;

			bender.tools.setHtmlWithSelection( editor, '<p>foo^bar</p>' );

			this.on( 'paste', function( evt ) {
				dataValueOnPaste = evt.data.dataValue;
				htmlDataOnPaste = evt.data.dataTransfer.getData( 'text/html' );
			}, 0 );

			pasteEventMock.$.clipboardData.setData( 'text/html', '<p>bam</p>' );
			editable.fire( 'paste', pasteEventMock );

			assertAfterPasteContent( this, '<p>foobambar</p>', function() {
				assert.areSame( '', dataValueOnPaste, 'Data value on paste (priority 0) should be empty.' );
				assert.areSame( '<p>bam</p>', htmlDataOnPaste, 'dataTransfer html data should be set.' );
			} );
			this.wait();
		},

		'paste with HTML in clipboardData - cancel on before paste': function() {
			if ( !CKEDITOR.plugins.clipboard.isCustomCopyCutSupported ) {
				assert.ignore();
			}

			var editor = this.editor,
				editable = editor.editable(),
				tc = this,
				pasteEventMock = bender.tools.mockPasteEvent(),
				pasteCount = 0,
				beforePasteCount = 0;

			bender.tools.setHtmlWithSelection( editor, '<p>foo^bar</p>' );

			this.on( 'beforePaste', function( evt ) {
				beforePasteCount++;
				evt.cancel();
			} );

			this.on( 'paste', function() {
				pasteCount++;
			}, 0 );

			pasteEventMock.$.clipboardData.setData( 'text/html', '<p>bam</p>' );
			editable.fire( 'paste', pasteEventMock );

			tc.wait( function() {
				assert.areSame( 1, beforePasteCount, 'There should be 1 before paste event.' );
				assert.areSame( 0, pasteCount, 'There should be no paste event.' );
			}, 50 );
		},

		'test cut': function() {
			if ( !CKEDITOR.plugins.clipboard.isCustomCopyCutSupported )
				assert.ignore();

			var editor = this.editor,
				editable = editor.editable(),
				pasteEventMock = bender.tools.mockPasteEvent();

			bender.tools.setHtmlWithSelection( editor, '<p>x[b<b>a</b>r]x</p>' );

			editable.fire( 'cut', pasteEventMock );

			// As Edge stores custom data in text/html it needs to be asserted differently - we need to extract content part (#962).
			if ( CKEDITOR.env.edge ) {
				var dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( {}, editor );
				assert.areSame( 'b<b>a</b>r', dataTransfer._.fallbackDataTransfer._extractDataComment( pasteEventMock.$.clipboardData.getData( 'text/html' ) ).content, 'HTML text' );
			} else {
				assert.areSame( 'b<b>a</b>r', pasteEventMock.$.clipboardData.getData( 'text/html' ), 'HTML text' );
			}

			assert.areSame( 'bar', pasteEventMock.$.clipboardData.getData( 'Text' ), 'Plain text' );
			assert.isInnerHtmlMatching( '<p>x^x@</p>', bender.tools.selection.getWithHtml( editor ), { compareSelection: true, normalizeSelection: true }, 'Editor content' );
			assert.areSame( pasteEventMock.$.clipboardData, CKEDITOR.plugins.clipboard.copyCutData.$, 'copyCutData should be initialized' );
		},

		'test copy': function() {
			if ( !CKEDITOR.plugins.clipboard.isCustomCopyCutSupported )
				assert.ignore();

			var editor = this.editor,
				editable = editor.editable(),
				pasteEventMock = bender.tools.mockPasteEvent();

			bender.tools.setHtmlWithSelection( editor, '<p>x[b<b>a</b>r]x</p>' );

			editable.fire( 'copy', pasteEventMock );

			// As Edge stores custom data in text/html it needs to be asserted differently - we need to extract content part (#962).
			if ( CKEDITOR.env.edge ) {
				var dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer( {}, editor );
				assert.areSame( 'b<b>a</b>r', dataTransfer._.fallbackDataTransfer._extractDataComment( pasteEventMock.$.clipboardData.getData( 'text/html' ) ).content, 'HTML text' );
			} else {
				assert.areSame( 'b<b>a</b>r', pasteEventMock.$.clipboardData.getData( 'text/html' ), 'HTML text' );
			}

			assert.areSame( 'bar', pasteEventMock.$.clipboardData.getData( 'Text' ), 'Plain text data' );
			assert.isInnerHtmlMatching( '<p>x[b<b>a</b>r]x@</p>', bender.tools.selection.getWithHtml( editor ), { compareSelection: true, normalizeSelection: true }, 'Editor content' );
			assert.areSame( pasteEventMock.$.clipboardData, CKEDITOR.plugins.clipboard.copyCutData.$, 'copyCutData should be initialized' );
		},

		'test cut and paste': function() {
			if ( !CKEDITOR.plugins.clipboard.isCustomCopyCutSupported )
				assert.ignore();

			var editor = this.editor,
				editable = editor.editable(),
				pasteEventMock = bender.tools.mockPasteEvent(),
				pasteCount = 0,
				pasteMethod, htmlData, textData, dataTransferType;

			bender.tools.setHtmlWithSelection( editor, '<p>x[b<b>a</b>r]x</p>' );

			editable.fire( 'cut', pasteEventMock );

			this.on( 'paste', function( evt ) {
				pasteCount++;
				pasteMethod = evt.data.method;
				htmlData = evt.data.dataTransfer.getData( 'text/html' );
				textData = evt.data.dataTransfer.getData( 'text/plain' );
				dataTransferType = evt.data.dataTransfer.getTransferType( evt.editor );
			} );

			bender.tools.setHtmlWithSelection( editor, '<p>foo^</p>' );
			editable.fire( 'paste', pasteEventMock );

			assertAfterPasteContent( this, '<p>foob<b>a</b>r</p>', function() {
				assert.areSame( 1, pasteCount, 'Paste count' );
				assert.areSame( 'paste', pasteMethod, 'Paste method.' );
				assert.areSame( 'b<b>a</b>r', htmlData, 'HTML data' );
				assert.areSame( 'bar', textData, 'Plain text data' );
				assert.areSame( CKEDITOR.DATA_TRANSFER_INTERNAL, dataTransferType, 'Trasfer type' );

			} );

			this.wait();
		},

		'test copy and paste': function() {
			if ( !CKEDITOR.plugins.clipboard.isCustomCopyCutSupported )
				assert.ignore();

			var editor = this.editor,
				editable = editor.editable(),
				pasteEventMock = bender.tools.mockPasteEvent(),
				pasteCount = 0,
				pasteMethod, htmlData, textData, dataTransferType;

			bender.tools.setHtmlWithSelection( editor, '<p>x[b<b>a</b>r]x</p>' );

			editable.fire( 'copy', pasteEventMock );

			this.on( 'paste', function( evt ) {
				pasteCount++;
				pasteMethod = evt.data.method;
				htmlData = evt.data.dataTransfer.getData( 'text/html' );
				textData = evt.data.dataTransfer.getData( 'text/plain' );
				dataTransferType = evt.data.dataTransfer.getTransferType( evt.editor );
			} );

			bender.tools.setHtmlWithSelection( editor, '<p>foo^</p>' );
			editable.fire( 'paste', pasteEventMock );

			assertAfterPasteContent( this, '<p>foob<b>a</b>r</p>', function() {
				assert.areSame( 1, pasteCount, 'Paste count' );
				assert.areSame( 'paste', pasteMethod, 'Paste method.' );
				assert.areSame( 'b<b>a</b>r', htmlData, 'HTML data' );
				assert.areSame( 'bar', textData, 'Plain text data' );
				assert.areSame( CKEDITOR.DATA_TRANSFER_INTERNAL, dataTransferType, 'Trasfer type' );

			} );

			this.wait();
		},

		'test paste if dataTransfer is not empty': function() {
			if ( !CKEDITOR.plugins.clipboard.isCustomCopyCutSupported )
				assert.ignore();

			var editor = this.editor,
				// As dataTransfer mock is used in `bender.tools.emulatePaste` we need to pass a type which is acceptable in Edge
				// as it does not support custom types (#962).
				customType = CKEDITOR.env.edge ? 'application/xml' : 'cke/custom',
				initialData = {};

			initialData[ customType ] = 'foo';

			this.on( 'paste', function( evt ) {
				resume( function() {
					assert.areSame( 'paste', evt.data.method, 'Paste method.' );
					assert.areSame( 'foo', evt.data.dataTransfer.getData( customType ), 'cke/custom data' );
					assert.areSame( '', evt.data.dataValue, 'dataValue' );
				} );
			} );

			bender.tools.emulatePaste( editor, '', initialData );

			this.wait();
		},

		'test no paste if dataTransfer and dataValue is empty': function() {
			var editor = this.editor,
				pasteCount = 0;

			this.on( 'paste', function() {
				pasteCount++;
			} );

			bender.tools.emulatePaste( editor, '' );

			this.wait( function() {
				assert.areSame( 0, pasteCount, 'Paste should not be fired.' );
			}, 0 );
		},

		// (#3415)
		'test paste list with whitespace on boundaries': function() {
			if ( !CKEDITOR.plugins.clipboard.isCustomCopyCutSupported ) {
				assert.ignore();
			}

			var editor = this.editor;

			this.on( 'afterPaste', function() {
				resume( function() {
					// If we got here, it means that the list was pasted successfully.
					assert.pass();
				} );
			} );

			bender.tools.emulatePaste( editor, '<html>' +
				'<body>\n' +
				'<!--StartFragment--><li>foo</li><!--EndFragment-->\n' +
				'</body>' +
				'</html>' );

			this.wait();
		},

		'test canClipboardApiBeTrusted internal': function() {
			var canClipboardApiBeTrusted = CKEDITOR.plugins.clipboard.canClipboardApiBeTrusted;

			var editor = this.editor,
				nativeData = bender.tools.mockNativeDataTransfer(),
				evt = { data: { $: { clipboardData: nativeData } } },
				dataTransfer = CKEDITOR.plugins.clipboard.initPasteDataTransfer( evt, editor );

			assert.isTrue( canClipboardApiBeTrusted( dataTransfer, editor ), 'Clipboard API should be used for internal operations.' );
		},

		'test canClipboardApiBeTrusted in Chrome': function() {
			if ( !CKEDITOR.env.chrome ) {
				assert.ignore();
			}

			var canClipboardApiBeTrusted = CKEDITOR.plugins.clipboard.canClipboardApiBeTrusted;

			var nativeData = bender.tools.mockNativeDataTransfer();

			nativeData.setData( 'text/html', '<b>foo</b>' );

			var evt = { data: { $: { clipboardData: nativeData } } },
				dataTransfer = CKEDITOR.plugins.clipboard.initPasteDataTransfer( evt );

			assert.isTrue( canClipboardApiBeTrusted( dataTransfer ), 'Clipboard API should be used in Chrome.' );
		},

		'test canClipboardApiBeTrusted in Safari': function() {
			if ( !CKEDITOR.env.safari ) {
				assert.ignore();
			}

			var canClipboardApiBeTrusted = CKEDITOR.plugins.clipboard.canClipboardApiBeTrusted,
				nativeData = bender.tools.mockNativeDataTransfer();

			nativeData.setData( 'text/html', '<b>foo</b>' );

			var evt = { data: { $: { clipboardData: nativeData } } },
				dataTransfer = CKEDITOR.plugins.clipboard.initPasteDataTransfer( evt );

			assert.isTrue( canClipboardApiBeTrusted( dataTransfer ), 'Clipboard API should be marked as trusted.' );
		},

		// #468
		'test canClipboardApiBeTrusted in Edge 16+': function() {
			if ( !trustyEdge ) {
				assert.ignore();
			}

			var canClipboardApiBeTrusted = CKEDITOR.plugins.clipboard.canClipboardApiBeTrusted,
				nativeData = bender.tools.mockNativeDataTransfer();

			nativeData.setData( 'text/html', '<b>foo</b>' );

			var evt = { data: { $: { clipboardData: nativeData } } },
				dataTransfer = CKEDITOR.plugins.clipboard.initPasteDataTransfer( evt );

			assert.isTrue( canClipboardApiBeTrusted( dataTransfer ), 'Clipboard API should be marked as trusted.' );
		},

		'test canClipboardApiBeTrusted in Android Chrome (no dataTransfer support)': function() {
			if ( !CKEDITOR.env.chrome ) {
				assert.ignore();
			}

			var canClipboardApiBeTrusted = CKEDITOR.plugins.clipboard.canClipboardApiBeTrusted;

			var dataTransfer = CKEDITOR.plugins.clipboard.initPasteDataTransfer(); // no native data transfer

			assert.isFalse( canClipboardApiBeTrusted( dataTransfer ),
				'Clipboard API should NOT be used for in Android Chrome.' );
		},

		'test canClipboardApiBeTrusted in Firefox with HTML': function() {
			if ( !CKEDITOR.env.gecko ) {
				assert.ignore();
			}

			var canClipboardApiBeTrusted = CKEDITOR.plugins.clipboard.canClipboardApiBeTrusted;

			var nativeData = bender.tools.mockNativeDataTransfer();

			nativeData.setData( 'text/html', '<b>foo</b>' );

			var evt = { data: { $: { clipboardData: nativeData } } },
				dataTransfer = CKEDITOR.plugins.clipboard.initPasteDataTransfer( evt );

			assert.isTrue( canClipboardApiBeTrusted( dataTransfer ), 'Clipboard API should be used in Firefox with HTML.' );
		},

		'test canClipboardApiBeTrusted in Firefox with files': function() {
			if ( !CKEDITOR.env.gecko ) {
				assert.ignore();
			}

			var canClipboardApiBeTrusted = CKEDITOR.plugins.clipboard.canClipboardApiBeTrusted;

			var nativeData = bender.tools.mockNativeDataTransfer();

			nativeData.files.push( 'foo' );

			var evt = { data: { $: { clipboardData: nativeData } } },
				dataTransfer = CKEDITOR.plugins.clipboard.initPasteDataTransfer( evt );

			assert.isTrue( canClipboardApiBeTrusted( dataTransfer ), 'Clipboard API should be used in Firefox with files.' );
		},

		'test canClipboardApiBeTrusted in Firefox without files and HTML': function() {
			if ( !CKEDITOR.env.gecko ) {
				assert.ignore();
			}

			var canClipboardApiBeTrusted = CKEDITOR.plugins.clipboard.canClipboardApiBeTrusted;

			var nativeData = bender.tools.mockNativeDataTransfer(),
				evt = { data: { $: { clipboardData: nativeData } } },
				dataTransfer = CKEDITOR.plugins.clipboard.initPasteDataTransfer( evt );

			assert.isFalse( canClipboardApiBeTrusted( dataTransfer ),
				'Clipboard API should not be used in Firefox without html and files.' );
		},

		'test canClipboardApiBeTrusted on other browser': function() {
			if ( CKEDITOR.env.chrome || CKEDITOR.env.gecko || CKEDITOR.env.safari || trustyEdge ) {
				assert.ignore();
			}

			var canClipboardApiBeTrusted = CKEDITOR.plugins.clipboard.canClipboardApiBeTrusted;

			var nativeData = bender.tools.mockNativeDataTransfer(),
				evt = { data: { $: { clipboardData: nativeData } } },
				dataTransfer = CKEDITOR.plugins.clipboard.initPasteDataTransfer( evt );

			assert.isFalse( canClipboardApiBeTrusted( dataTransfer ), 'Clipboard API should not be used in other browsers.' );
		},

		'#131 - trailing spaces': function() {
			assertPasteEvent(
				this.editor,
				{ dataValue: '&nbsp; BBB&nbsp;' },
				{ type: 'text', dataValue: '&nbsp; BBB&nbsp;' },
				'preserve trailing spaces'
			);
		},

		'#131 - trailing spaces - default config.clipboard_defaultContentType': function() {
			// Test for default content - type.
			testEditor( this, {},
				function( editor ) {
					assertPasteEvent( editor,
						{ dataValue: '&nbsp; BBB&nbsp;' },
						{ type: 'html', dataValue: '&nbsp; BBB&nbsp;' },
						'preserve trailing spaces' );
				} );
		},

		// https://dev.ckeditor.com/ticket/9675 and https://dev.ckeditor.com/ticket/9534.
		'strip editable when about to paste the entire inline editor': function() {
			// https://dev.ckeditor.com/ticket/9534: FF and Webkits in inline editor based on header element.
			assertPasteEvent( this.editor, { dataValue: '<h1 class="cke_editable">Foo<br>Bar</h1>' },
				{ dataValue: 'Foo<br>Bar' }, 'stripped .cke_editable' );

			assertPasteEvent( this.editor, { dataValue: '<div class="cke_contents">Bar<br></div>' },
				{ dataValue: 'Bar' }, 'stripped .cke_contents & bogus br removed' );

			// https://dev.ckeditor.com/ticket/9675: FF36 copies divarea.
			assertPasteEvent( this.editor,
				{ dataValue: '<div id="cke_1_contents" class="cke_contents"><div class="cke_editable" contenteditable="true"><p>aaa</p></div></div>' },
				{ dataValue: '<p>aaa</p>' }, 'stripped .cke_editable > .cke_contents' );
		}
	} );

} )();
