ソースを参照

Converted engine.treeView.Writer tests to use new view util functions.

Szymon Kupś 9 年 前
コミット
9da843217a

+ 60 - 264
packages/ckeditor5-engine/tests/treeview/writer/breakattributes.js

@@ -9,294 +9,90 @@
 
 import Writer from '/ckeditor5/engine/treeview/writer.js';
 import ContainerElement from '/ckeditor5/engine/treeview/containerelement.js';
-import AttributeElement from '/ckeditor5/engine/treeview/attributeelement.js';
 import Text from '/ckeditor5/engine/treeview/text.js';
-import utils from '/tests/engine/treeview/writer/_utils/utils.js';
+import Position from '/ckeditor5/engine/treeview/position.js';
+import { stringify, parse } from '/tests/engine/_utils/view.js';
 
 describe( 'Writer', () => {
-	const create = utils.create;
-	const test = utils.test;
 	let writer;
 
+	/**
+	 * Executes test using `parse` and `stringify` utils functions. Uses range delimiters `[]{}` to create and
+	 * test break position.
+	 *
+	 * @param {String} input
+	 * @param {String} expected
+	 */
+	function test( input, expected ) {
+		const { view, selection } = parse( input );
+		const newPosition = writer.breakAttributes( selection.getFirstPosition() );
+		expect( stringify( view, newPosition, { showType: true, showPriority: true } ) ).to.equal( expected );
+	}
+
 	beforeEach( () => {
 		writer = new Writer();
 	} );
 
 	describe( 'breakAttributes', () => {
-		// <p>{|foobar}</p> -> <p>|{foobar}</p>
-		it( '<p>{|foobar}</p>', () => {
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobar', position: 0 }
-				]
-			} );
-
-			const newPosition = writer.breakAttributes( created.position );
-
-			test( writer, newPosition, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				position: 0,
-				children: [
-					{ instanceOf: Text, data: 'foobar' }
-				]
-			} );
+		it( 'should move position from begin of text node to the element', () => {
+			test( '<container:p>{}foobar</container:p>', '<container:p>[]foobar</container:p>' );
 		} );
 
-		it( '<p>foo|bar</p>', () => {
-			// <p>{foo|bar}</p> -> <p>{foo}|{bar}</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobar', position: 3 }
-				]
-			} );
+		it( 'should split text node', () => {
+			const text = new Text( 'foobar' );
+			const container = new ContainerElement( 'p', null, text );
+			const position = new Position( text, 3 );
 
-			const newPosition = writer.breakAttributes( created.position );
+			const newPosition = writer.breakAttributes( position );
 
-			test( writer, newPosition, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				position: 1,
-				children: [
-					{ instanceOf: Text, data: 'foo' },
-					{ instanceOf: Text, data: 'bar' }
-				]
-			} );
+			expect( container.getChildCount() ).to.equal( 2 );
+			expect( container.getChild( 0 ) ).to.be.instanceOf( Text ).and.have.property( 'data' ).that.equal( 'foo' );
+			expect( container.getChild( 1 ) ).to.be.instanceOf( Text ).and.have.property( 'data' ).that.equal( 'bar' );
+			expect( newPosition.isEqual( new Position( container, 1 ) ) ).to.be.true;
 		} );
 
-		it( '<p>{foobar|}</p>', () => {
-			// <p>{foobar|}</p> -> <p>{foobar}|</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobar', position: 6 }
-				]
-			} );
-
-			const newPosition = writer.breakAttributes( created.position );
-
-			test( writer, newPosition, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				position: 1,
-				children: [
-					{ instanceOf: Text, data: 'foobar' }
-				]
-			} );
+		it( 'should move position from end of text node to the element', () => {
+			test( '<container:p>foobar{}</container:p>', '<container:p>foobar[]</container:p>' );
 		} );
 
-		it( '<p><b>{foo|bar}</b></p>', () => {
-			// <p><b>{foo|bar}</b></p> -> <p><b>{foo}</b>|<b>{bar}</b></p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'foobar', position: 3 }
-						]
-					}
-				]
-			} );
-
-			const newPosition = writer.breakAttributes( created.position );
-
-			test( writer, newPosition, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				position: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'foo' }
-						]
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'bar' }
-						]
-					}
-				]
-			} );
+		it( 'should split attribute element', () => {
+			test(
+				'<container:p><attribute:b:1>foo{}bar</attribute:b:1></container:p>',
+				'<container:p><attribute:b:1>foo</attribute:b:1>[]<attribute:b:1>bar</attribute:b:1></container:p>'
+			);
 		} );
 
-		it( '<p><b><u>{|foobar}</u></b></p>', () => {
-			// <p><b><u>{|foobar}</u></b></p> -> <p>|<b><u>{foobar}</u></b></p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{
-								instanceOf: AttributeElement,
-								name: 'u',
-								priority: 1,
-								children: [
-									{ instanceOf: Text, data: 'foobar', position: 0 }
-								]
-							}
-						]
-					}
-				]
-			} );
-
-			const newPosition = writer.breakAttributes( created.position );
-
-			test( writer, newPosition, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				position: 0,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{
-								instanceOf: AttributeElement,
-								name: 'u',
-								priority: 1,
-								children: [
-									{ instanceOf: Text, data: 'foobar' }
-								]
-							}
-						]
-					}
-				]
-			} );
+		it( 'should move from beginning of the nested text node to the container', () => {
+			test(
+				'<container:p><attribute:b:1><attribute:u:1>{}foobar</attribute:u:1></attribute:b:1></container:p>',
+				'<container:p>[]<attribute:b:1><attribute:u:1>foobar</attribute:u:1></attribute:b:1></container:p>'
+			);
 		} );
 
-		// <p><b><u>{foo|ba}r</u></b></p> -> <p><b><u>{foo}</u></b>|<b></u>{bar}</u></b></p>
-		it( '<p><b><u>{foo|bar}</u></b></p>', () => {
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{
-								instanceOf: AttributeElement,
-								name: 'u',
-								priority: 1,
-								children: [
-									{ instanceOf: Text, data: 'foobar', position: 3 }
-								]
-							}
-						]
-					}
-				]
-			} );
-
-			const newPosition = writer.breakAttributes( created.position );
-
-			test( writer, newPosition, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				position: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{
-								instanceOf: AttributeElement,
-								name: 'u',
-								priority: 1,
-								children: [
-									{ instanceOf: Text, data: 'foo' }
-								]
-							}
-						]
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{
-								instanceOf: AttributeElement,
-								name: 'u',
-								priority: 1,
-								children: [
-									{ instanceOf: Text, data: 'bar' }
-								]
-							}
-						]
-					}
-				]
-			} );
+		it( 'should split nested attributes', () => {
+			test(
+				'<container:p><attribute:b:1><attribute:u:1>foo{}bar</attribute:u:1></attribute:b:1></container:p>',
+				'<container:p>' +
+					'<attribute:b:1>' +
+						'<attribute:u:1>' +
+							'foo' +
+						'</attribute:u:1>' +
+					'</attribute:b:1>' +
+					'[]' +
+					'<attribute:b:1>' +
+						'<attribute:u:1>' +
+							'bar' +
+						'</attribute:u:1>' +
+					'</attribute:b:1>' +
+				'</container:p>'
+			);
 		} );
 
-		it( '<p><b><u>{foobar|}</u></b></p>', () => {
-			// <p><b><u>{foobar|}</u></b></p> -> <p><b><u>{foobar}</u></b>|</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{
-								instanceOf: AttributeElement,
-								name: 'u',
-								priority: 1,
-								children: [
-									{ instanceOf: Text, data: 'foobar', position: 6 }
-								]
-							}
-						]
-					}
-				]
-			} );
-
-			const newPosition = writer.breakAttributes( created.position );
-
-			test( writer, newPosition, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				position: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{
-								instanceOf: AttributeElement,
-								name: 'u',
-								priority: 1,
-								children: [
-									{ instanceOf: Text, data: 'foobar' }
-								]
-							}
-						]
-					}
-				]
-			} );
+		it( 'should move from end of the nested text node to the container', () => {
+			test(
+				'<container:p><attribute:b:1><attribute:u:1>foobar{}</attribute:u:1></attribute:b:1></container:p>',
+				'<container:p><attribute:b:1><attribute:u:1>foobar</attribute:u:1></attribute:b:1>[]</container:p>'
+			);
 		} );
 	} );
 } );

+ 41 - 179
packages/ckeditor5-engine/tests/treeview/writer/breakrange.js

@@ -9,16 +9,24 @@
 
 import Writer from '/ckeditor5/engine/treeview/writer.js';
 import ContainerElement from '/ckeditor5/engine/treeview/containerelement.js';
-import AttributeElement from '/ckeditor5/engine/treeview/attributeelement.js';
 import Range from '/ckeditor5/engine/treeview/range.js';
-import Text from '/ckeditor5/engine/treeview/text.js';
-import utils from '/tests/engine/treeview/writer/_utils/utils.js';
+import { stringify, parse } from '/tests/engine/_utils/view.js';
 
 describe( 'Writer', () => {
-	const create = utils.create;
-	const test = utils.test;
 	let writer;
 
+	/**
+	 * Executes test using `parse` and `stringify` utils functions.
+	 *
+	 * @param {String} input
+	 * @param {String} expected
+	 */
+	function test( input, expected ) {
+		const { view, selection } = parse( input );
+		const newRange = writer.breakRange( selection.getFirstRange() );
+		expect( stringify( view, newRange, { showType: true, showPriority: true } ) ).to.equal( expected );
+	}
+
 	beforeEach( () => {
 		writer = new Writer();
 	} );
@@ -34,198 +42,52 @@ describe( 'Writer', () => {
 		} );
 
 		it( 'should break at collapsed range and return collapsed one', () => {
-			// <p>{foo[]bar}</p> -> <p>{foo}[]{bar}</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobar', rangeStart: 3, rangeEnd: 3 }
-				]
-			} );
-
-			const newRange = writer.breakRange( created.range );
-
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 1,
-				rangeEnd: 1,
-				children: [
-					{ instanceOf: Text, data: 'foo' },
-					{ instanceOf: Text, data: 'bar' }
-				]
-			} );
+			test(
+				'<container:p>foo{}bar</container:p>',
+				'<container:p>foo[]bar</container:p>'
+			);
 		} );
 
 		it( 'should break inside text node #1', () => {
-			// <p>{foo[bar]baz}</p> -> <p>{foo}[{bar}]{baz}</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobarbaz', rangeStart: 3, rangeEnd: 6 }
-				]
-			} );
-
-			const newRange = writer.breakRange( created.range );
-
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 1,
-				rangeEnd: 2,
-				children: [
-					{ instanceOf: Text, data: 'foo' },
-					{ instanceOf: Text, data: 'bar' },
-					{ instanceOf: Text, data: 'baz' }
-				]
-			} );
+			test(
+				'<container:p>foo{bar}baz</container:p>',
+				'<container:p>foo[bar]baz</container:p>'
+			);
 		} );
 
 		it( 'should break inside text node #2', () => {
-			// <p>{foo[barbaz]}</p> -> <p>{foo}[{barbaz}]</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobarbaz', rangeStart: 3, rangeEnd: 9 }
-				]
-			} );
-
-			const newRange = writer.breakRange( created.range );
-
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 1,
-				rangeEnd: 2,
-				children: [
-					{ instanceOf: Text, data: 'foo' },
-					{ instanceOf: Text, data: 'barbaz' }
-				]
-			} );
+			test(
+				'<container:p>foo{barbaz}</container:p>',
+				'<container:p>foo[barbaz]</container:p>'
+			);
 		} );
 
 		it( 'should break inside text node #3', () => {
-			// <p>{foo[barbaz}]</p> -> <p>{foo}[{barbaz}]</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeEnd: 1,
-				children: [
-					{ instanceOf: Text, data: 'foobarbaz', rangeStart: 3 }
-				]
-			} );
-
-			const newRange = writer.breakRange( created.range );
-
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 1,
-				rangeEnd: 2,
-				children: [
-					{ instanceOf: Text, data: 'foo' },
-					{ instanceOf: Text, data: 'barbaz' }
-				]
-			} );
+			test(
+				'<container:p>foo{barbaz]</container:p>',
+				'<container:p>foo[barbaz]</container:p>'
+			);
 		} );
 
 		it( 'should break inside text node #4', () => {
-			// <p>{[foo]barbaz}</p> -> <p>[{foo}]{barbaz]</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobarbaz', rangeStart: 0, rangeEnd: 3 }
-				]
-			} );
-
-			const newRange = writer.breakRange( created.range );
-
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{ instanceOf: Text, data: 'foo' },
-					{ instanceOf: Text, data: 'barbaz' }
-				]
-			} );
+			test(
+				'<container:p>{foo}barbaz</container:p>',
+				'<container:p>[foo]barbaz</container:p>'
+			);
 		} );
 
 		it( 'should break inside text node #5', () => {
-			// <p>[{foo]barbaz}</p> -> <p>[{foo}]{barbaz]</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				children: [
-					{ instanceOf: Text, data: 'foobarbaz', rangeEnd: 3 }
-				]
-			} );
-
-			const newRange = writer.breakRange( created.range );
-
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{ instanceOf: Text, data: 'foo' },
-					{ instanceOf: Text, data: 'barbaz' }
-				]
-			} );
+			test(
+				'<container:p>[foo}barbaz</container:p>',
+				'<container:p>[foo]barbaz</container:p>'
+			);
 		} );
 
 		it( 'should break placed inside different nodes', () => {
-			// <p>{foo[bar}<b>{baz]qux}</b></p>
-			// <p>{foo}[{bar}<b>{baz}</b>]<b>qux</b></p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobar', rangeStart: 3 },
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'bazqux', rangeEnd: 3 }
-						]
-					}
-				]
-			} );
-
-			const newRange = writer.breakRange( created.range );
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 1,
-				rangeEnd: 3,
-				children: [
-					{ instanceOf: Text, data: 'foo' },
-					{ instanceOf: Text, data: 'bar' },
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'baz' }
-						]
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'qux' }
-						]
-					}
-				]
-			} );
+			test(
+				'<container:p>foo{bar<attribute:b:1>baz}qux</attribute:b:1></container:p>',
+				'<container:p>foo[bar<attribute:b:1>baz</attribute:b:1>]<attribute:b:1>qux</attribute:b:1></container:p>'
+			);
 		} );
 	} );
 } );

+ 78 - 324
packages/ckeditor5-engine/tests/treeview/writer/insert.js

@@ -8,366 +8,120 @@
 'use strict';
 
 import Writer from '/ckeditor5/engine/treeview/writer.js';
-import ContainerElement from '/ckeditor5/engine/treeview/containerelement.js';
-import AttributeElement from '/ckeditor5/engine/treeview/attributeelement.js';
-import Position from '/ckeditor5/engine/treeview/position.js';
-import Text from '/ckeditor5/engine/treeview/text.js';
-import utils from '/tests/engine/treeview/writer/_utils/utils.js';
+import { stringify, parse } from '/tests/engine/_utils/view.js';
 
 describe( 'Writer', () => {
-	const create = utils.create;
-	const test = utils.test;
 	let writer;
 
+	/**
+	 * Executes test using `parse` and `stringify` utils functions.
+	 *
+	 * @param {String} input
+	 * @param {Array.<String>} nodesToInsert
+	 * @param {String} expected
+	 */
+	function test( input, nodesToInsert, expected ) {
+		nodesToInsert = nodesToInsert.map( node => parse( node ) );
+		const { view, selection } = parse( input );
+		const newRange = writer.insert( selection.getFirstPosition(), nodesToInsert );
+		expect( stringify( view, newRange, { showType: true, showPriority: true } ) ).to.equal( expected );
+	}
+
 	beforeEach( () => {
 		writer = new Writer();
 	} );
 
 	describe( 'insert', () => {
 		it( 'should return collapsed range in insertion position when using empty array', () => {
-			// <p>{foo|bar}</p> -> <p>{foo[]bar}</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobar', position: 3 }
-				]
-			} );
-
-			const newRange = writer.insert( created.position, [] );
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobar', rangeStart: 3, rangeEnd: 3 }
-				]
-			} );
+			test(
+				'<container:p>foo{}bar</container:p>',
+				[],
+				'<container:p>foo{}bar</container:p>'
+			);
 		} );
 
 		it( 'should insert text into another text node #1', () => {
-			// <p>{foo|bar}</p> insert {baz}
-			// <p>{foo[baz]bar}</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobar', position: 3 }
-				]
-			} );
-
-			const newRange = writer.insert( created.position, new Text( 'baz' ) );
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobazbar', rangeStart: 3, rangeEnd: 6 }
-				]
-			} );
+			test(
+				'<container:p>foo{}bar</container:p>',
+				[ 'baz' ],
+				'<container:p>foo{baz}bar</container:p>'
+			);
 		} );
 
 		it( 'should insert text into another text node #2', () => {
-			// <p>{foobar|}</p> insert {baz}
-			// <p>{foobar[baz}]</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobar', position: 6 }
-				]
-			} );
-
-			const newRange = writer.insert( created.position, new Text( 'baz' ) );
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeEnd: 1,
-				children: [
-					{ instanceOf: Text, data: 'foobarbaz', rangeStart: 6 }
-				]
-			} );
+			test(
+				'<container:p>foobar{}</container:p>',
+				[ 'baz' ],
+				'<container:p>foobar{baz]</container:p>'
+			);
 		} );
 
 		it( 'should insert text into another text node #3', () => {
-			// <p>{|foobar}</p> insert {baz}
-			// <p>[{baz]foobar}</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobar', position: 0 }
-				]
-			} );
-
-			const newRange = writer.insert( created.position, new Text( 'baz' ) );
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				children: [
-					{ instanceOf: Text, data: 'bazfoobar', rangeEnd: 3 }
-				]
-			} );
+			test(
+				'<container:p>{}foobar</container:p>',
+				[ 'baz' ],
+				'<container:p>[baz}foobar</container:p>'
+			);
 		} );
 
 		it( 'should break attributes when inserting into text node', () => {
-			// <p>{foo|bar}</p> insert <b>{baz}</b>
-			// <p>{foo}[<b>baz</b>]{bar}</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobar', position: 3 }
-				]
-			} );
-			const toInsert = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'b',
-				priority: 1,
-				children: [
-					{ instanceOf: Text, data: 'baz' }
-				]
-			} );
-
-			const newRange = writer.insert( created.position, toInsert.node );
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 1,
-				rangeEnd: 2,
-				children: [
-					{ instanceOf: Text, data: 'foo' },
-					{
-						instanceOf: ContainerElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'baz' }
-						]
-					},
-					{ instanceOf: Text, data: 'bar' }
-				]
-			} );
+			test(
+				'<container:p>foo{}bar</container:p>',
+				[ '<attribute:b:1>baz</attribute:b:1>' ],
+				'<container:p>foo[<attribute:b:1>baz</attribute:b:1>]bar</container:p>'
+			);
 		} );
 
-		it( 'should merge text ndoes', () => {
-			// <p>|{foobar}</p> insert {baz}
-			// <p>[{baz]foobar}</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				position: 0,
-				children: [
-					{ instanceOf: Text, data: 'foobar' }
-				]
-			} );
-
-			const newRange = writer.insert( created.position, new Text( 'baz' ) );
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				children: [
-					{ instanceOf: Text, data: 'bazfoobar', rangeEnd: 3 }
-				]
-			} );
+		it( 'should merge text nodes', () => {
+			test(
+				'<container:p>[]foobar</container:p>',
+				[ 'baz' ],
+				'<container:p>[baz}foobar</container:p>'
+			);
 		} );
 
 		it( 'should merge same attribute nodes', () => {
-			// <p><b>{foo|bar}</b></p> insert <b>{baz}</b>
-			// <p><b>{foo[baz]bar}</b></p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'foobar', position: 3 }
-						]
-					}
-				]
-			} );
-			const toInsert = create( writer, {
-				instanceOf: AttributeElement,
-				name: 'b',
-				priority: 1,
-				children: [
-					{ instanceOf: Text, data: 'baz' }
-				]
-			} );
-
-			const newRange = writer.insert( created.position, toInsert.node );
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'foobazbar', rangeStart: 3, rangeEnd: 6 }
-						]
-					}
-				]
-			} );
+			test(
+				'<container:p><attribute:b:1>foo{}bar</attribute:b:1></container:p>',
+				[ '<attribute:b:1>baz</attribute:b:1>' ],
+				'<container:p><attribute:b:1>foo{baz}bar</attribute:b:1></container:p>'
+			);
 		} );
 
 		it( 'should not merge different attributes', () => {
-			// <p><b>{foo|bar}</b></p> insert <b>{baz}</b> ( different priority )
-			// <p><b>{foo}</b>[<b>{baz}</b>]<b>{bar}</b></p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'foobar', position: 3 }
-						]
-					}
-				]
-			} );
-			const toInsert = create( writer, {
-				instanceOf: AttributeElement,
-				name: 'b',
-				priority: 2,
-				children: [
-					{ instanceOf: Text, data: 'baz' }
-				]
-			} );
-
-			const newRange = writer.insert( created.position, toInsert.node );
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 1,
-				rangeEnd: 2,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'foo' }
-						]
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 2,
-						children: [
-							{ instanceOf: Text, data: 'baz' }
-						]
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'bar' }
-						]
-					}
-				]
-			} );
+			test(
+				'<container:p><attribute:b:1>foo{}bar</attribute:b:1></container:p>',
+				[ '<attribute:b:2>baz</attribute:b:2>' ],
+				'<container:p>' +
+					'<attribute:b:1>' +
+						'foo' +
+					'</attribute:b:1>' +
+					'[' +
+					'<attribute:b:2>' +
+						'baz' +
+					'</attribute:b:2>' +
+					']' +
+					'<attribute:b:1>' +
+						'bar' +
+					'</attribute:b:1>' +
+				'</container:p>'
+			);
 		} );
 
 		it( 'should allow to insert multiple nodes', () => {
-			// <p>|</p> insert <b>{foo}</b>{bar}
-			// <p>[<b>{foo}</b>{bar}]</p>
-			const root = new ContainerElement( 'p' );
-			const toInsert = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'fake',
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'foo' }
-						]
-					},
-					{ instanceOf: Text, data: 'bar' }
-				]
-			} ).node.getChildren();
-			const position = new Position( root, 0 );
-
-			const newRange = writer.insert( position, toInsert );
-			test( writer, newRange, root, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 2,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'foo' }
-						]
-					},
-					{ instanceOf: Text, data: 'bar' }
-				]
-			} );
+			test(
+				'<container:p>[]</container:p>',
+				[ '<attribute:b:1>foo</attribute:b:1>', 'bar' ],
+				'<container:p>[<attribute:b:1>foo</attribute:b:1>bar]</container:p>'
+			);
 		} );
 
 		it( 'should merge after inserting multiple nodes', () => {
-			// <p><b>{qux}</b>|{baz}</p> insert <b>{foo}</b>{bar}
-			// <p><b>{qux[foo}</b>{bar]baz}</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				position: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'qux' }
-						]
-					},
-					{ instanceOf: Text, data: 'baz' }
-				]
-			} );
-			const toInsert = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'fake',
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'foo' }
-						]
-					},
-					{ instanceOf: Text, data: 'bar' }
-				]
-			} ).node.getChildren();
-
-			const newRange = writer.insert( created.position, toInsert );
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'quxfoo', rangeStart: 3 }
-						]
-					},
-					{ instanceOf: Text, data: 'barbaz', rangeEnd: 3 }
-				]
-			} );
+			test(
+				'<container:p><attribute:b:1>qux</attribute:b:1>[]baz</container:p>',
+				[ '<attribute:b:1>foo</attribute:b:1>', 'bar' ],
+				'<container:p><attribute:b:1>qux{foo</attribute:b:1>bar}baz</container:p>'
+			);
 		} );
 	} );
 } );

+ 56 - 226
packages/ckeditor5-engine/tests/treeview/writer/mergeattributes.js

@@ -9,270 +9,100 @@
 
 import Writer from '/ckeditor5/engine/treeview/writer.js';
 import ContainerElement from '/ckeditor5/engine/treeview/containerelement.js';
-import AttributeElement from '/ckeditor5/engine/treeview/attributeelement.js';
 import Text from '/ckeditor5/engine/treeview/text.js';
-import utils from '/tests/engine/treeview/writer/_utils/utils.js';
+import Position from '/ckeditor5/engine/treeview/position.js';
+import { stringify, parse } from '/tests/engine/_utils/view.js';
 
 describe( 'Writer', () => {
-	const create = utils.create;
-	const test = utils.test;
 	let writer;
 
+	/**
+	 * Executes test using `parse` and `stringify` utils functions. Uses range delimiters `[]{}` to create and
+	 * test merge position.
+	 *
+	 * @param {String} input
+	 * @param {String} expected
+	 */
+	function test( input, expected ) {
+		const { view, selection } = parse( input );
+		const newPosition = writer.mergeAttributes( selection.getFirstPosition() );
+		expect( stringify( view, newPosition, { showType: true, showPriority: true } ) ).to.equal( expected );
+	}
+
 	beforeEach( () => {
 		writer = new Writer();
 	} );
 
 	describe( 'mergeAttributes', () => {
 		it( 'should not merge if inside text node', () => {
-			// <p>{fo|obar}</p>
-			const description = {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobar', position: 2 }
-				]
-			};
-			const created = create( writer, description );
-			const newPosition = writer.mergeAttributes( created.position );
-			test( writer, newPosition, created.node, description );
+			test( '<container:p>fo{}bar</container:p>', '<container:p>fo{}bar</container:p>' );
 		} );
 
 		it( 'should not merge if between containers', () => {
-			// <div><p>{foo}</p>|<p>{bar}</p></div>
-			const description = {
-				instanceOf: ContainerElement,
-				name: 'div',
-				position: 1,
-				children: [
-					{
-						instanceOf: ContainerElement,
-						name: 'p',
-						children: [
-							{ instanceOf: Text, data: 'foo' }
-						]
-					},
-					{
-						instanceOf: ContainerElement,
-						name: 'p',
-						children: [
-							{ instanceOf: Text, data: 'bar' }
-						]
-					}
-				]
-			};
-			const created = create( writer, description );
-			const newPosition = writer.mergeAttributes( created.position );
-			test( writer, newPosition, created.node, description );
+			test(
+				'<container:div><container:p>foo</container:p>[]<container:p>bar</container:p></container:div>',
+				'<container:div><container:p>foo</container:p>[]<container:p>bar</container:p></container:div>'
+			);
 		} );
 
 		it( 'should return same position when inside empty container', () => {
-			// <p>|</p>
-			const description = { instanceOf: ContainerElement, name: 'p', position: 0 };
-			const created = create( writer, description );
-			const newPosition = writer.mergeAttributes( created.position );
-			test( writer, newPosition, created.node, description );
+			test(
+				'<container:p>[]</container:p>',
+				'<container:p>[]</container:p>'
+			);
 		} );
 
 		it( 'should not merge when position is placed at the beginning of the container', () => {
-			// <p>|<b></b></p>
-			const description = {
-				instanceOf: ContainerElement,
-				name: 'p',
-				position: 0,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1
-					}
-				]
-			};
-			const created = create( writer, description );
-			const newPosition = writer.mergeAttributes( created.position );
-			test( writer, newPosition, created.node, description );
+			test(
+				'<container:p>[]<attribute:b:1></attribute:b:1></container:p>',
+				'<container:p>[]<attribute:b:1></attribute:b:1></container:p>'
+			);
 		} );
 
 		it( 'should not merge when position is placed at the end of the container', () => {
-			// <p><b></b>|</p>
-			const description = {
-				instanceOf: ContainerElement,
-				name: 'p',
-				position: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1
-					}
-				]
-			};
-			const created = create( writer, description );
-			const newPosition = writer.mergeAttributes( created.position );
-			test( writer, newPosition, created.node, description );
+			test(
+				'<container:p><attribute:b:1></attribute:b:1>[]</container:p>',
+				'<container:p><attribute:b:1></attribute:b:1>[]</container:p>'
+			);
 		} );
 
 		it( 'should merge when placed between two text nodes', () => {
-			// <p>{foo}|{bar}</p> -> <p>{foo|bar}</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				position: 1,
-				children: [
-					{ instanceOf: Text, data: 'foo' },
-					{ instanceOf: Text, data: 'bar' }
-				]
-			} );
-
-			const newPosition = writer.mergeAttributes( created.position );
-
-			test( writer, newPosition, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobar', position: 3 }
-				]
-			} );
+			// <p>foobar</p> -> <p>foo|bar</p>
+			const t1 = new Text( 'foo' );
+			const t2 = new Text( 'bar' );
+			const p = new ContainerElement( 'p', null, [ t1, t2 ] );
+			const position = new Position( p, 1 );
+
+			const newPosition = writer.mergeAttributes( position );
+			expect( stringify( p, newPosition ) ).to.equal( '<p>foo{}bar</p>' );
 		} );
 
 		it( 'should merge when placed between similar attribute nodes', () => {
-			// <p><b foo="bar"></b>|<b foo="bar"></b></p> -> <p><b foo="bar">|</b></p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				position: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						attributes: { foo: 'bar' }
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						attributes: { foo: 'bar' }
-					}
-				]
-			} );
-
-			const newPosition = writer.mergeAttributes( created.position );
-
-			test( writer, newPosition, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						position: 0,
-						attributes: { foo: 'bar' }
-					}
-				]
-			} );
+			test(
+				'<container:p><attribute:b:1 foo="bar"></attribute:b:1>[]<attribute:b:1 foo="bar"></attribute:b:1></container:p>',
+				'<container:p><attribute:b:1 foo="bar">[]</attribute:b:1></container:p>'
+			);
 		} );
 
 		it( 'should not merge when placed between non-similar attribute nodes', () => {
-			// <p><b foo="bar"></b>|<b foo="baz"></b></p> ->
-			// <p><b foo="bar"></b>|<b foo="baz"></b></p>
-			const description = {
-				instanceOf: ContainerElement,
-				name: 'p',
-				position: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						attributes: { foo: 'bar' }
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						attributes: { foo: 'baz' }
-					}
-				]
-			};
-			const created = create( writer, description );
-			const newPosition = writer.mergeAttributes( created.position );
-			test( writer, newPosition, created.node, description );
+			test(
+				'<container:p><attribute:b:1 foo="bar"></attribute:b:1>[]<attribute:b:1 foo="baz"></attribute:b:1></container:p>',
+				'<container:p><attribute:b:1 foo="bar"></attribute:b:1>[]<attribute:b:1 foo="baz"></attribute:b:1></container:p>'
+			);
 		} );
 
 		it( 'should not merge when placed between similar attribute nodes with different priority', () => {
-			// <p><b foo="bar"></b>|<b foo="bar"></b></p> -> <p><b foo="bar"></b>|<b foo="bar"></b></p>
-			const description =  {
-				instanceOf: ContainerElement,
-				name: 'p',
-				position: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						attributes: { foo: 'bar' }
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 2,
-						attributes: { foo: 'bar' }
-					}
-				]
-			};
-			const created = create( writer, description );
-			const newPosition = writer.mergeAttributes( created.position );
-			test( writer, newPosition, created.node, description );
+			test(
+				'<container:p><attribute:b:1 foo="bar"></attribute:b:1>[]<attribute:b:2 foo="bar"></attribute:b:2></container:p>',
+				'<container:p><attribute:b:1 foo="bar"></attribute:b:1>[]<attribute:b:2 foo="bar"></attribute:b:2></container:p>'
+			);
 		} );
 
 		it( 'should merge attribute nodes and their contents if possible', () => {
-			// <p><b foo="bar">{foo}</b>|<b foo="bar">{bar}</b></p>
-			// <p><b foo="bar">{foo}|{bar}</b></p>
-			// <p><b foo="bar">{foo|bar}</b></p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				position: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						attributes: { foo: 'bar' },
-						children: [
-							{ instanceOf: Text, data: 'foo' }
-						]
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						attributes: { foo: 'bar' },
-						children: [
-							{ instanceOf: Text, data: 'bar' }
-						]
-					}
-				]
-			} );
-
-			const newPosition = writer.mergeAttributes( created.position );
-
-			test( writer, newPosition, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						attributes: { foo: 'bar' },
-						children: [
-							{ instanceOf: Text, data: 'foobar', position: 3 }
-						]
-					}
-				]
-			} );
+			test(
+				'<container:p><attribute:b:1 foo="bar">foo</attribute:b:1>[]<attribute:b:1 foo="bar">bar</attribute:b:1></container:p>',
+				'<container:p><attribute:b:1 foo="bar">foo{}bar</attribute:b:1></container:p>'
+			);
 		} );
 	} );
 } );

+ 35 - 226
packages/ckeditor5-engine/tests/treeview/writer/remove.js

@@ -9,17 +9,29 @@
 
 import Writer from '/ckeditor5/engine/treeview/writer.js';
 import ContainerElement from '/ckeditor5/engine/treeview/containerelement.js';
-import AttributeElement from '/ckeditor5/engine/treeview/attributeelement.js';
 import Range from '/ckeditor5/engine/treeview/range.js';
-import Text from '/ckeditor5/engine/treeview/text.js';
 import DocumentFragment from '/ckeditor5/engine/treeview/documentfragment.js';
-import utils from '/tests/engine/treeview/writer/_utils/utils.js';
+import { stringify, parse } from '/tests/engine/_utils/view.js';
 
 describe( 'Writer', () => {
-	const create = utils.create;
-	const test = utils.test;
 	let writer;
 
+	/**
+	 * Executes test using `parse` and `stringify` utils functions. Uses range delimiters `[]{}` to create and
+	 * test ranges.
+	 *
+	 * @param {String} input
+	 * @param {String} expectedResult
+	 * @param {String} expectedRemoved
+	 */
+	function test( input, expectedResult, expectedRemoved ) {
+		const { view, selection } = parse( input );
+		const range = selection.getFirstRange();
+		const removed = writer.remove( range );
+		expect( stringify( view, range, { showType: true, showPriority: true } ) ).to.equal( expectedResult );
+		expect( stringify( removed, null, { showType: true, showPriority: true } ) ).to.equal( expectedRemoved );
+	}
+
 	beforeEach( () => {
 		writer = new Writer();
 	} );
@@ -45,242 +57,39 @@ describe( 'Writer', () => {
 		} );
 
 		it( 'should remove single text node', () => {
-			// <p>[{foobar}]</p> -> <p>|</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{ instanceOf: Text, data: 'foobar' }
-				]
-			} );
-
-			const removed = writer.remove( created.range );
-			test( writer, created.range.start, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				position: 0,
-				children: []
-			} );
-
-			// Test removed nodes.
-			test( writer, null, Array.from( removed.getChildren() ), [
-				{ instanceOf: Text, data: 'foobar' }
-			] );
+			test( '<container:p>[foobar]</container:p>', '<container:p>[]</container:p>', 'foobar' );
 		} );
 
 		it( 'should not leave empty text nodes', () => {
-			// <p>{[foobar]}</p> -> <p>|</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobar', rangeStart: 0, rangeEnd: 6 }
-				]
-			} );
-
-			const removed = writer.remove( created.range );
-			test( writer, created.range.start, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				position: 0,
-				children: []
-			} );
-
-			// Test removed nodes.
-			test( writer, null, removed, [
-				{ instanceOf: Text, data: 'foobar' }
-			] );
+			test( '<container:p>{foobar}</container:p>', '<container:p>[]</container:p>', 'foobar' );
 		} );
 
 		it( 'should remove part of the text node', () => {
-			// <p>{f[oob]ar}</p> -> <p>{f|ar}</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobar', rangeStart: 1, rangeEnd: 4 }
-				]
-			} );
-
-			const removed = writer.remove( created.range );
-			test( writer, created.range.start, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'far', position: 1 }
-				]
-			} );
-
-			// Test removed nodes.
-			test( writer, null, removed, [
-				{ instanceOf: Text, data: 'oob' }
-			] );
+			test( '<container:p>f{oob}ar</container:p>', '<container:p>f{}ar</container:p>', 'oob' );
 		} );
 
 		it( 'should remove parts of nodes', () => {
-			// <p>{f[oo}<b>{ba]r}</b></p> -> <p>{f}|<b>r</b></p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foo', rangeStart: 1 },
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'bar', rangeEnd: 2 }
-						]
-					}
-				]
-			} );
-
-			const removed = writer.remove( created.range );
-			test( writer, created.range.start, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				position: 1,
-				children: [
-					{ instanceOf: Text, data: 'f' },
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'r' }
-						]
-					}
-				]
-			} );
-
-			// Test removed nodes.
-			test( writer, null, removed, [
-				{ instanceOf: Text, data: 'oo' },
-				{
-					instanceOf: AttributeElement,
-					name: 'b',
-					priority: 1,
-					children: [
-						{ instanceOf: Text, data: 'ba' }
-					]
-				}
-			] );
+			test(
+				'<container:p>f{oo<attribute:b:10>ba}r</attribute:b:10></container:p>',
+				'<container:p>f[]<attribute:b:10>r</attribute:b:10></container:p>',
+				'oo<attribute:b:10>ba</attribute:b:10>'
+			);
 		} );
 
 		it( 'should merge after removing #1', () => {
-			// <p><b>foo</b>[{bar}]<b>bazqux</b></p> -> <p><b>foo|bazqux</b></p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 1,
-				rangeEnd: 2,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'foo' }
-						]
-					},
-					{ instanceOf: Text, data: 'bar' },
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'bazqux' }
-						]
-					}
-				]
-			} );
-
-			const removed = writer.remove( created.range );
-			test( writer, created.range.start, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'foobazqux', position: 3 }
-						]
-					}
-				]
-			} );
-
-			// Test removed nodes.
-			test( writer, null, removed, [
-				{ instanceOf: Text, data: 'bar' }
-			] );
+			test(
+				'<container:p><attribute:b:1>foo</attribute:b:1>[bar]<attribute:b:1>bazqux</attribute:b:1></container:p>',
+				'<container:p><attribute:b:1>foo{}bazqux</attribute:b:1></container:p>',
+				'bar'
+			);
 		} );
 
 		it( 'should merge after removing #2', () => {
-			// <p><b>fo[o</b>{bar}<b>ba]zqux</b></p> -> <p><b>fo|zqux</b></p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'foo', rangeStart: 2 }
-						]
-					},
-					{ instanceOf: Text, data: 'bar' },
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'bazqux', rangeEnd: 2 }
-						]
-					}
-				]
-			} );
-
-			const removed = writer.remove( created.range );
-			test( writer, created.range.start, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'fozqux', position: 2 }
-						]
-					}
-				]
-			} );
-
-			// Test removed nodes.
-			test( writer, null, removed, [
-				{
-					instanceOf: AttributeElement,
-					name: 'b',
-					priority: 1,
-					children: [
-						{ instanceOf: Text, data: 'o' }
-					]
-				},
-				{ instanceOf: Text, data: 'bar' },
-				{
-					instanceOf: AttributeElement,
-					name: 'b',
-					priority: 1,
-					children: [
-						{ instanceOf: Text, data: 'ba' }
-					]
-				}
-			] );
+			test(
+				'<container:p><attribute:b:1>fo{o</attribute:b:1>bar<attribute:b:1>ba}zqux</attribute:b:1></container:p>',
+				'<container:p><attribute:b:1>fo{}zqux</attribute:b:1></container:p>',
+				'<attribute:b:1>o</attribute:b:1>bar<attribute:b:1>ba</attribute:b:1>'
+			);
 		} );
 	} );
 } );

+ 164 - 814
packages/ckeditor5-engine/tests/treeview/writer/unwrap.js

@@ -14,50 +14,44 @@ import AttributeElement from '/ckeditor5/engine/treeview/attributeelement.js';
 import Position from '/ckeditor5/engine/treeview/position.js';
 import Range from '/ckeditor5/engine/treeview/range.js';
 import Text from '/ckeditor5/engine/treeview/text.js';
-import utils from '/tests/engine/treeview/writer/_utils/utils.js';
 import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
+import { stringify, parse } from '/tests/engine/_utils/view.js';
 
 describe( 'Writer', () => {
-	const create = utils.create;
-	const test = utils.test;
 	let writer;
 
+	/**
+	 * Executes test using `parse` and `stringify` utils functions.
+	 *
+	 * @param {String} input
+	 * @param {String} unwrapAttribute
+	 * @param {String} expected
+	 */
+	function test( input, unwrapAttribute, expected ) {
+		const { view, selection } = parse( input );
+		const newRange = writer.unwrap( selection.getFirstRange(), parse( unwrapAttribute ) );
+		expect( stringify( view, newRange, { showType: true, showPriority: true } ) ).to.equal( expected );
+	}
+
 	beforeEach( () => {
 		writer = new Writer();
 	} );
 
 	describe( 'unwrap', () => {
 		it( 'should do nothing on collapsed ranges', () => {
-			const description = {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foo', rangeStart: 1, rangeEnd: 1 }
-				]
-			};
-			const created = create( writer, description );
-			const newRange = writer.unwrap( created.range, new AttributeElement( 'b' ), 1 );
-			test( writer, newRange, created.node, description );
+			test(
+				'<container:p>f{}oo</container:p>',
+				'<attribute:b:10></attribute:b:10>',
+				'<container:p>f{}oo</container:p>'
+			);
 		} );
 
 		it( 'should do nothing on single text node', () => {
-			// <p>[{foobar}]</p>
-			const description = {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{ instanceOf: Text, data: 'foobar' }
-				]
-			};
-
-			const created = create( writer, description );
-			const b = new AttributeElement( 'b' );
-			b.priority = 1;
-			const newRange = writer.unwrap( created.range, b );
-
-			test( writer, newRange, created.node, description );
+			test(
+				'<container:p>[foobar]</container:p>',
+				'<attribute:b:1></attribute:b:1>',
+				'<container:p>[foobar]</container:p>'
+			);
 		} );
 
 		it( 'should throw error when element is not instance of AttributeElement', () => {
@@ -88,841 +82,197 @@ describe( 'Writer', () => {
 		} );
 
 		it( 'should unwrap single node', () => {
-			// <p>[<b>{foobar}</b>]<p> -> <p>[{foobar}]</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'foobar' }
-						]
-					}
-				]
-			} );
-
-			const b = new AttributeElement( 'b' );
-			b.priority = 1;
-			const newRange = writer.unwrap( created.range, b );
-
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{ instanceOf: Text, data: 'foobar' }
-				]
-			} );
+			test(
+				'<container:p>[<attribute:b:1>foobar</attribute:b:1>]</container:p>',
+				'<attribute:b:1></attribute:b:1>',
+				'<container:p>[foobar]</container:p>'
+			);
 		} );
 
 		it( 'should not unwrap attributes with different priorities #1', () => {
-			// <p>[<b>{foobar}</b>]<p> -> <p>[<b>{foobar}</b>]</p>
-			// Unwrapped with <b> but using different priority.
-			const description =  {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'foobar' }
-						]
-					}
-				]
-			};
-			const created = create( writer, description );
-
-			const b = new AttributeElement( 'b' );
-			b.priority = 2;
-			const newRange = writer.unwrap( created.range, b );
-
-			test( writer, newRange, created.node, description );
+			test(
+				'<container:p>[<attribute:b:1>foobar</attribute:b:1>]</container:p>',
+				'<attribute:b:2></attribute:b:2>',
+				'<container:p>[<attribute:b:1>foobar</attribute:b:1>]</container:p>'
+			);
 		} );
 
 		it( 'should not unwrap attributes with different priorities #2', () => {
-			// <p>[<b>{foo}</b><b>{bar}</b><b>{baz}</b>]<p> -> <p>[{foo}<b>bar</b>{baz}]</p>
-			// <b> around `bar` has different priority than others.
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 3,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 2,
-						children: [
-							{ instanceOf: Text, data: 'foo' }
-						]
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'bar' }
-						]
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 2,
-						children: [
-							{ instanceOf: Text, data: 'baz' }
-						]
-					}
-				]
-			} );
-
-			const b = new AttributeElement( 'b' );
-			b.priority = 2;
-			const newRange = writer.unwrap( created.range, b );
-
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 3,
-				children: [
-					{ instanceOf: Text, data: 'foo' },
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'bar' }
-						]
-					},
-					{ instanceOf: Text, data: 'baz' }
-				]
-			} );
+			test(
+				'<container:p>' +
+				'[' +
+					'<attribute:b:2>foo</attribute:b:2>' +
+					'<attribute:b:1>bar</attribute:b:1>' +
+					'<attribute:b:2>baz</attribute:b:2>' +
+				']' +
+				'</container:p>',
+				'<attribute:b:2></attribute:b:2>',
+				'<container:p>[foo<attribute:b:1>bar</attribute:b:1>baz]</container:p>'
+			);
 		} );
 
 		it( 'should unwrap part of the node', () => {
-			// <p>[{baz}<b>{foo]bar}</b><p> -> <p>[{bazfoo}]<b>{bar}</b></p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				children: [
-					{ instanceOf: Text, data: 'baz' },
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'foobar', rangeEnd: 3 }
-						]
-
-					}
-				]
-			} );
-
-			const b = new AttributeElement( 'b' );
-			b.priority = 1;
-
-			const newRange = writer.unwrap( created.range, b );
-
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{ instanceOf: Text, data: 'bazfoo' },
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'bar' }
-						]
-
-					}
-				]
-			} );
+			test(
+				'<container:p>[baz<attribute:b:1>foo}bar</attribute:b:1>',
+				'<attribute:b:1></attribute:b:1>',
+				'<container:p>[bazfoo]<attribute:b:1>bar</attribute:b:1></container:p>'
+			);
 		} );
 
 		it( 'should unwrap nested attributes', () => {
-			// <p>[<u><b>{foobar}</b></u>]</p> -> <p>[<u>{foobar}</u>]</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'u',
-						priority: 1,
-						children: [
-							{
-								instanceOf: AttributeElement,
-								name: 'b',
-								priority: 1,
-								children: [
-									{ instanceOf: Text, data: 'foobar' }
-								]
-							}
-						]
-					}
-				]
-			} );
-			const b = new AttributeElement( 'b' );
-			b.priority = 1;
-
-			const newRange = writer.unwrap( created.range, b );
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'u',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'foobar' }
-						]
-					}
-				]
-			} );
+			test(
+				'<container:p>[<attribute:u:1><attribute:b:1>foobar</attribute:b:1></attribute:u:1>]</container:p>',
+				'<attribute:b:1></attribute:b:1>',
+				'<container:p>[<attribute:u:1>foobar</attribute:u:1>]</container:p>'
+			);
 		} );
 
 		it( 'should merge unwrapped nodes #1', () => {
-			// <p>{foo}[<b>{bar}</b>]{bom}</p> -> <p>{foo[bar]bom}</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 1,
-				rangeEnd: 2,
-				children: [
-					{ instanceOf: Text, data: 'foo' },
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'bar' }
-						]
-					},
-					{ instanceOf: Text, data: 'bom' }
-				]
-			} );
-
-			const b =  new AttributeElement( 'b' );
-			b.priority = 1;
-			const newRange = writer.unwrap( created.range, b );
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{
-						instanceOf: Text,
-						data: 'foobarbom',
-						rangeStart: 3,
-						rangeEnd: 6
-					}
-				]
-			} );
+			test(
+				'<container:p>foo[<attribute:b:1>bar</attribute:b:1>]baz</container:p>',
+				'<attribute:b:1></attribute:b:1>',
+				'<container:p>foo{bar}baz</container:p>'
+			);
 		} );
 
 		it( 'should merge unwrapped nodes #2', () => {
-			// <p>{foo}<u>{bar}</u>[<b><u>{bazqux}</u></b>]</p> -> <p>{foo}<u>{bar[bazqux}</u>]</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 2,
-				rangeEnd: 3,
-				children: [
-					{ instanceOf: Text, data: 'foo' },
-					{
-						instanceOf: AttributeElement,
-						name: 'u',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'bar' }
-						]
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{
-								instanceOf: AttributeElement,
-								name: 'u',
-								priority: 1,
-								children: [
-									{ instanceOf: Text, data: 'bazqux' }
-								]
-							}
-						]
-					}
-				]
-			} );
-
-			const b = new AttributeElement( 'b' );
-			b.priority = 1;
-			const newRange = writer.unwrap( created.range, b );
-
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeEnd: 2,
-				children: [
-					{
-						instanceOf: Text,
-						data: 'foo'
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'u',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'barbazqux', rangeStart: 3 }
-						]
-					}
-				]
-			} );
+			const input = '<container:p>' +
+			'foo' +
+				'<attribute:u:1>bar</attribute:u:1>' +
+				'[' +
+				'<attribute:b:1>' +
+					'<attribute:u:1>bazqux</attribute:u:1>' +
+				'</attribute:b:1>' +
+				']' +
+			'</container:p>';
+			const attribute = '<attribute:b:1></attribute:b:1>';
+			const result = '<container:p>foo<attribute:u:1>bar{bazqux</attribute:u:1>]</container:p>';
+
+			test( input, attribute, result );
 		} );
 
 		it( 'should merge unwrapped nodes #3', () => {
-			// <p>{foo}<u>{bar}</u>[<b><u>{baz]qux}</u></b></p> -> <p>{foo}<u>{bar[baz}</u>]<b><u>{qux}</u></b></p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 2,
-				children: [
-					{ instanceOf: Text, data: 'foo' },
-					{
-						instanceOf: AttributeElement,
-						name: 'u',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'bar' }
-						]
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{
-								instanceOf: AttributeElement,
-								name: 'u',
-								priority: 1,
-								children: [
-									{ instanceOf: Text, data: 'bazqux', rangeEnd: 3 }
-								]
-							}
-						]
-					}
-				]
-			} );
-
-			const b = new AttributeElement( 'b' );
-			b.priority = 1;
-			const newRange = writer.unwrap( created.range, b );
-
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeEnd: 2,
-				children: [
-					{
-						instanceOf: Text,
-						data: 'foo'
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'u',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'barbaz', rangeStart: 3 }
-						]
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{
-								instanceOf: AttributeElement,
-								name: 'u',
-								priority: 1,
-								children: [
-									{ instanceOf: Text, data: 'qux' }
-								]
-							}
-						]
-					}
-				]
-			} );
+			const input = '<container:p>' +
+				'foo' +
+				'<attribute:u:1>bar</attribute:u:1>' +
+				'[' +
+				'<attribute:b:1>' +
+					'<attribute:u:1>baz}qux</attribute:u:1>' +
+				'</attribute:b:1>' +
+			'</container:p>';
+			const attribute = '<attribute:b:1></attribute:b:1>';
+			const result = '<container:p>' +
+				'foo' +
+				'<attribute:u:1>bar{baz</attribute:u:1>]' +
+				'<attribute:b:1>' +
+					'<attribute:u:1>qux</attribute:u:1>' +
+				'</attribute:b:1>' +
+			'</container:p>';
+
+			test( input, attribute, result );
 		} );
 
 		it( 'should merge unwrapped nodes #4', () => {
-			// <p>{foo}<u>{bar}</u>[<b><u>{baz}</u></b>]<u>qux</u></p> -> <p>{foo}<u>{bar[baz]qux}</u></p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 2,
-				rangeEnd: 3,
-				children: [
-					{ instanceOf: Text, data: 'foo' },
-					{
-						instanceOf: AttributeElement,
-						name: 'u',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'bar' }
-						]
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{
-								instanceOf: AttributeElement,
-								name: 'u',
-								priority: 1,
-								children: [
-									{ instanceOf: Text, data: 'baz' }
-								]
-							}
-						]
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'u',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'qux' }
-						]
-					}
-				]
-			} );
-
-			const b = new AttributeElement( 'b' );
-			b.priority = 1;
-			const newRange = writer.unwrap( created.range, b );
-
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{
-						instanceOf: Text,
-						data: 'foo'
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'u',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'barbazqux', rangeStart: 3, rangeEnd: 6 }
-						]
-					}
-				]
-			} );
+			const input = '<container:p>' +
+				'foo' +
+				'<attribute:u:1>bar</attribute:u:1>' +
+				'[' +
+				'<attribute:b:1>' +
+					'<attribute:u:1>baz</attribute:u:1>' +
+				'</attribute:b:1>' +
+				']' +
+				'<attribute:u:1>qux</attribute:u:1>' +
+			'</container:p>';
+			const attribute = '<attribute:b:1></attribute:b:1>';
+			const result = '<container:p>' +
+				'foo' +
+				'<attribute:u:1>bar{baz}qux</attribute:u:1>' +
+			'</container:p>';
+
+			test( input, attribute, result );
 		} );
 
 		it( 'should merge unwrapped nodes #5', () => {
-			// <p>[<b><u>{foo}</u></b><b><u>{bar}</u></b><b><u>{baz}</u></b>]</p> -> <p>[<u>{foobarbaz}</u>]</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 3,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{
-								instanceOf: AttributeElement,
-								name: 'u',
-								priority: 1,
-								children: [
-									{ instanceOf: Text, data: 'foo' }
-								]
-							}
-						]
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{
-								instanceOf: AttributeElement,
-								name: 'u',
-								priority: 1,
-								children: [
-									{ instanceOf: Text, data: 'bar' }
-								]
-							}
-						]
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 1,
-						children: [
-							{
-								instanceOf: AttributeElement,
-								name: 'u',
-								priority: 1,
-								children: [
-									{ instanceOf: Text, data: 'baz' }
-								]
-							}
-						]
-					}
-				]
-			} );
-
-			const b = new AttributeElement( 'b' );
-			b.priority = 1;
-			const newRange = writer.unwrap( created.range, b );
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'u',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'foobarbaz' }
-						]
-					}
-				]
-			} );
+			const input = '<container:p>' +
+				'[' +
+				'<attribute:b:1><attribute:u:1>foo</attribute:u:1></attribute:b:1>' +
+				'<attribute:b:1><attribute:u:1>bar</attribute:u:1></attribute:b:1>' +
+				'<attribute:b:1><attribute:u:1>baz</attribute:u:1></attribute:b:1>' +
+				']' +
+			'</container:p>';
+			const attribute = '<attribute:b:1></attribute:b:1>';
+			const result = '<container:p>[<attribute:u:1>foobarbaz</attribute:u:1>]</container:p>';
+
+			test( input, attribute, result );
 		} );
 
 		it( 'should unwrap mixed ranges #1', () => {
-			// <p>[<u><b>{foo}]</b></u></p> -> <p>[<u>{foo}</u>]</p
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'u',
-						priority: 1,
-						children: [
-							{
-								instanceOf: AttributeElement,
-								name: 'b',
-								priority: 1,
-								rangeEnd: 1,
-								children: [
-									{ instanceOf: Text, data: 'foo' }
-								]
-							}
-						]
-					}
-				]
-			} );
-			const b = new AttributeElement( 'b' );
-			b.priority = 1;
-			const newRange = writer.unwrap( created.range, b );
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'u',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'foo' }
-						]
-					}
-				]
-			} );
+			const input = '<container:p>' +
+				'[' +
+				'<attribute:u:1>' +
+					'<attribute:b:1>foo]</attribute:b:1>' +
+				'</attribute:u:1>' +
+			'</container:p>';
+			const attribute = '<attribute:b:1></attribute:b:1>';
+			const result = '<container:p>[<attribute:u:1>foo</attribute:u:1>]</container:p>';
+
+			test( input, attribute, result );
 		} );
 
 		it( 'should unwrap mixed ranges #2', () => {
-			// <p>[<u><b>{foo]}</b></u></p> -> <p>[<u>{foo}</u>]</p
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'u',
-						priority: 1,
-						children: [
-							{
-								instanceOf: AttributeElement,
-								name: 'b',
-								priority: 1,
-								children: [
-									{ instanceOf: Text, data: 'foo', rangeEnd: 3 }
-								]
-							}
-						]
-					}
-				]
-			} );
-			const b = new AttributeElement( 'b' );
-			b.priority = 1;
-			const newRange = writer.unwrap( created.range, b );
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'u',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'foo' }
-						]
-					}
-				]
-			} );
+			test(
+				'<container:p>[<attribute:u:1><attribute:b:1>foo}</attribute:b:1></attribute:u></container:p>',
+				'<attribute:b:1></attribute:b:1>',
+				'<container:p>[<attribute:u:1>foo</attribute:u:1>]</container:p>'
+			);
 		} );
 
 		it( 'should unwrap single element by removing matching attributes', () => {
-			// <p>[<b foo="bar" baz="qux" >test</b>]</p>
-			// unwrap using <b baz="qux"></b>
-			// <p>[<b foo="bar">test</b>]</p>
-			const text = new Text( 'test' );
-			const b = new AttributeElement( 'b', {
-				foo: 'bar',
-				baz: 'qux'
-			}, text );
-			const p = new ContainerElement( 'p', null, b );
-			const wrapper = new AttributeElement( 'b', {
-				baz: 'qux'
-			} );
-			const range = Range.createFromParentsAndOffsets( p, 0, p, 1 );
-
-			const newRange = writer.unwrap( range, wrapper );
-			expect( b.getAttribute( 'foo' ) ).to.equal( 'bar' );
-			expect( b.hasAttribute( 'baz' ) ).to.be.false;
-			expect( b.parent ).to.equal( p );
-			test( writer, newRange, p, {
-				instanceof: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceof: AttributeElement,
-						name: 'b',
-						children: [
-							{ instanceof: Text, data: 'test' }
-						]
-					}
-				]
-			} );
+			test(
+				'<container:p>[<attribute:b:1 foo="bar" baz="qux">test</attribute:b:1>]</container:p>',
+				'<attribute:b:1 baz="qux"></attribute:b:1>',
+				'<container:p>[<attribute:b:1 foo="bar">test</attribute:b:1>]</container:p>'
+			);
 		} );
 
 		it( 'should not unwrap single element when attributes are different', () => {
-			// <p>[<b foo="bar" baz="qux">test</b>]</p>
-			// unwrap using <b baz="qux" test="true"></b>
-			// <p>[<b foo="bar" baz="qux">test</b>]</p>
-			const text = new Text( 'test' );
-			const b = new AttributeElement( 'b', {
-				foo: 'bar',
-				baz: 'qux'
-			}, text );
-			const p = new ContainerElement( 'p', null, b );
-			const wrapper = new AttributeElement( 'b', {
-				baz: 'qux',
-				test: 'true'
-			} );
-			const range = Range.createFromParentsAndOffsets( p, 0, p, 1 );
-
-			const newRange = writer.unwrap( range, wrapper );
-			expect( b.getAttribute( 'foo' ) ).to.equal( 'bar' );
-			expect( b.getAttribute( 'baz' ) ).to.equal( 'qux' );
-			expect( b.parent ).to.equal( p );
-			test( writer, newRange, p, {
-				instanceof: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceof: AttributeElement,
-						name: 'b',
-						children: [
-							{ instanceof: Text, data: 'test' }
-						]
-					}
-				]
-			} );
+			test(
+				'<container:p>[<attribute:b:1 foo="bar" baz="qux">test</attribute:b:1>]</container:p>',
+				'<attribute:b:1 baz="qux" test="true"></attribute:b:1>',
+				'<container:p>[<attribute:b:1 foo="bar" baz="qux">test</attribute:b:1>]</container:p>'
+			);
 		} );
 
 		it( 'should unwrap single element by removing matching classes', () => {
-			// <p>[<b class="foo bar baz">test</b>]</p>
-			// unwrap using <b class="baz foo"></b>
-			// <p>[<b class="bar">test</b>]</p>
-			const text = new Text( 'test' );
-			const b = new AttributeElement( 'b', {
-				class: 'foo bar baz'
-			}, text );
-			const p = new ContainerElement( 'p', null, b );
-			const wrapper = new AttributeElement( 'b', {
-				class: 'baz foo'
-			} );
-			const range = Range.createFromParentsAndOffsets( p, 0, p, 1 );
-
-			const newRange = writer.unwrap( range, wrapper );
-			expect( b.hasClass( 'bar' ) ).to.be.true;
-			expect( b.hasClass( 'foo' ) ).to.be.false;
-			expect( b.hasClass( 'baz' ) ).to.be.false;
-			expect( b.parent ).to.equal( p );
-			test( writer, newRange, p, {
-				instanceof: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceof: AttributeElement,
-						name: 'b',
-						children: [
-							{ instanceof: Text, data: 'test' }
-						]
-					}
-				]
-			} );
+			test(
+				'<container:p>[<attribute:b:1 class="foo bar baz">test</attribute:b:1>]</container:p>',
+				'<attribute:b:1 class="baz foo"></attribute:b:1>',
+				'<container:p>[<attribute:b:1 class="bar">test</attribute:b:1>]</container:p>'
+			);
 		} );
 
 		it( 'should not unwrap single element when classes are different', () => {
-			// <p>[<b class="foo bar baz">test</b>]</p>
-			// unwrap using <b class="baz foo qux"></b>
-			// <p>[<b class="foo bar baz">test</b>]</p>
-			const text = new Text( 'test' );
-			const b = new AttributeElement( 'b', {
-				class: 'foo bar baz'
-			}, text );
-			const p = new ContainerElement( 'p', null, b );
-			const wrapper = new AttributeElement( 'b', {
-				class: 'baz foo qux'
-			} );
-			const range = Range.createFromParentsAndOffsets( p, 0, p, 1 );
-
-			const newRange = writer.unwrap( range, wrapper );
-			expect( b.hasClass( 'bar' ) ).to.be.true;
-			expect( b.hasClass( 'foo' ) ).to.be.true;
-			expect( b.hasClass( 'baz' ) ).to.be.true;
-			expect( b.parent ).to.equal( p );
-			test( writer, newRange, p, {
-				instanceof: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceof: AttributeElement,
-						name: 'b',
-						children: [
-							{ instanceof: Text, data: 'test' }
-						]
-					}
-				]
-			} );
+			test(
+				'<container:p>[<attribute:b:1 class="foo bar baz">test</attribute:b:1>]</container:p>',
+				'<attribute:b:1 class="baz foo qux"></attribute:b:1>',
+				'<container:p>[<attribute:b:1 class="foo bar baz">test</attribute:b:1>]</container:p>'
+			);
 		} );
 
 		it( 'should unwrap single element by removing matching styles', () => {
-			// <p>[<b style="color:red; position:absolute; top:10px;">test</b>]</p>
-			// unwrap using <b style="position: absolute;"></b>
-			// <p>[<b style="color:red; top:10px;">test</b>]</p>
-			const text = new Text( 'test' );
-			const b = new AttributeElement( 'b', {
-				style: 'color:red; position:absolute; top:10px;'
-			}, text );
-			const p = new ContainerElement( 'p', null, b );
-			const wrapper = new AttributeElement( 'b', {
-				style: 'position: absolute;'
-			} );
-			const range = Range.createFromParentsAndOffsets( p, 0, p, 1 );
-
-			const newRange = writer.unwrap( range, wrapper );
-			expect( b.getStyle( 'color' ) ).to.equal( 'red' );
-			expect( b.getStyle( 'top' ) ).to.equal( '10px' );
-			expect( b.hasStyle( 'position' ) ).to.be.false;
-			expect( b.parent ).to.equal( p );
-			test( writer, newRange, p, {
-				instanceof: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceof: AttributeElement,
-						name: 'b',
-						children: [
-							{ instanceof: Text, data: 'test' }
-						]
-					}
-				]
-			} );
+			test(
+				'<container:p>[<attribute:b:1 style="color:red;position:absolute;top:10px;">test</attribute:b:1>]</container:p>',
+				'<attribute:b:1 style="position: absolute;"></attribute:b:1>',
+				'<container:p>[<attribute:b:1 style="color:red;top:10px;">test</attribute:b:1>]</container:p>'
+			);
 		} );
 
 		it( 'should not unwrap single element when styles are different', () => {
-			// <p>[<b style="color:red; position:absolute; top:10px;">test</b>]</p>
-			// unwrap using <b style="position: relative;"></b>
-			// <p>[<b style="color:red; position:absolute; top:10px;">test</b>]</p>
-			const text = new Text( 'test' );
-			const b = new AttributeElement( 'b', {
-				style: 'color:red; position:absolute; top:10px;'
-			}, text );
-			const p = new ContainerElement( 'p', null, b );
-			const wrapper = new AttributeElement( 'b', {
-				style: 'position: relative;'
-			} );
-			const range = Range.createFromParentsAndOffsets( p, 0, p, 1 );
-
-			const newRange = writer.unwrap( range, wrapper );
-			expect( b.getStyle( 'color' ) ).to.equal( 'red' );
-			expect( b.getStyle( 'top' ) ).to.equal( '10px' );
-			expect( b.getStyle( 'position' ) ).to.equal( 'absolute' );
-			expect( b.parent ).to.equal( p );
-			test( writer, newRange, p, {
-				instanceof: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceof: AttributeElement,
-						name: 'b',
-						children: [
-							{ instanceof: Text, data: 'test' }
-						]
-					}
-				]
-			} );
+			test(
+				'<container:p>[<attribute:b:1 style="color:red;position:absolute;top:10px;">test</attribute:b:1>]</container:p>',
+				'<attribute:b:1 style="position: relative;"></attribute:b:1>',
+				'<container:p>[<attribute:b:1 style="color:red;position:absolute;top:10px;">test</attribute:b:1>]</container:p>'
+			);
 		} );
 	} );
 } );

+ 135 - 663
packages/ckeditor5-engine/tests/treeview/writer/wrap.js

@@ -11,69 +11,47 @@ import Writer from '/ckeditor5/engine/treeview/writer.js';
 import Element from '/ckeditor5/engine/treeview/element.js';
 import ContainerElement from '/ckeditor5/engine/treeview/containerelement.js';
 import AttributeElement from '/ckeditor5/engine/treeview/attributeelement.js';
-import { DEFAULT_PRIORITY } from '/ckeditor5/engine/treeview/attributeelement.js';
 import Position from '/ckeditor5/engine/treeview/position.js';
 import Range from '/ckeditor5/engine/treeview/range.js';
 import Text from '/ckeditor5/engine/treeview/text.js';
-import utils from '/tests/engine/treeview/writer/_utils/utils.js';
 import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
+import { stringify, parse } from '/tests/engine/_utils/view.js';
 
 describe( 'Writer', () => {
-	const create = utils.create;
-	const test = utils.test;
 	let writer;
 
+	/**
+	 * Executes test using `parse` and `stringify` utils functions.
+	 *
+	 * @param {String} input
+	 * @param {String} unwrapAttribute
+	 * @param {String} expected
+	 */
+	function test( input, unwrapAttribute, expected ) {
+		const { view, selection } = parse( input );
+		const newRange = writer.wrap( selection.getFirstRange(), parse( unwrapAttribute ) );
+		expect( stringify( view, newRange, { showType: true, showPriority: true } ) ).to.equal( expected );
+	}
+
 	beforeEach( () => {
 		writer = new Writer();
 	} );
 
 	describe( 'wrap', () => {
 		it( 'should do nothing on collapsed ranges', () => {
-			const description = {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foo', rangeStart: 1, rangeEnd: 1 }
-				]
-			};
-			const created = create( writer, description );
-			const newRange = writer.wrap( created.range, new AttributeElement( 'b' ) );
-			test( writer, newRange, created.node, description );
+			test(
+				'<container:p>f{}oo</container:p>',
+				'<attribute:b></attribute:b>',
+				'<container:p>f{}oo</container:p>'
+			);
 		} );
 
 		it( 'wraps single text node', () => {
-			// <p>[{foobar}]</p>
-			// wrap <b>
-			// <p>[<b>{foobar}<b>]</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{ instanceOf: Text, data: 'foobar' }
-				]
-			} );
-
-			const b = new AttributeElement( 'b' );
-			const newRange = writer.wrap( created.range, b );
-
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: DEFAULT_PRIORITY,
-						children: [
-							{ instanceOf: Text, data: 'foobar' }
-						]
-					}
-				]
-			} );
+			test(
+				'<container:p>[foobar]</container:p>',
+				'<attribute:b:1></attribute:b:1>',
+				'<container:p>[<attribute:b:1>foobar</attribute:b:1>]</container:p>'
+			);
 		} );
 
 		it( 'should throw error when element is not instance of AttributeElement', () => {
@@ -104,671 +82,165 @@ describe( 'Writer', () => {
 		} );
 
 		it( 'wraps part of a single text node #1', () => {
-			// <p>[{foo]bar}</p>
-			// wrap with <b>
-			// <p>[<b>{foo}</b>]{bar}</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				children: [
-					{ instanceOf: Text, data: 'foobar', rangeEnd: 3 }
-				]
-			} );
-
-			const b = new AttributeElement( 'b' );
-			const newRange = writer.wrap( created.range, b );
-
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: DEFAULT_PRIORITY,
-						children: [
-							{ instanceOf: Text, data: 'foo' }
-						]
-					},
-					{ instanceOf: Text, data: 'bar' }
-				]
-			} );
+			test(
+				'<container:p>[foo}bar</container:p>',
+				'<attribute:b:1></attribute:b:1>',
+				'<container:p>[<attribute:b:1>foo</attribute:b:1>]bar</container:p>'
+			);
 		} );
 
 		it( 'wraps part of a single text node #2', () => {
-			// <p>{[foo]bar}</p>
-			// wrap with <b>
-			// <p>[<b>{foo}</b>]{bar}</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobar', rangeStart: 0, rangeEnd: 3 }
-				]
-			} );
-
-			const b = new AttributeElement( 'b' );
-			const newRange = writer.wrap( created.range, b );
-
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: DEFAULT_PRIORITY,
-						children: [
-							{ instanceOf: Text, data: 'foo' }
-						]
-					},
-					{ instanceOf: Text, data: 'bar' }
-				]
-			} );
+			test(
+				'<container:p>{foo}bar</container:p>',
+				'<attribute:b:1></attribute:b:1>',
+				'<container:p>[<attribute:b:1>foo</attribute:b:1>]bar</container:p>'
+			);
 		} );
 
 		it( 'wraps part of a single text node #3', () => {
-			// <p>{foo[bar]}</p>
-			// wrap with <b>
-			// <p>{foo}[<b>{bar}</b>]</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				children: [
-					{ instanceOf: Text, data: 'foobar', rangeStart: 3, rangeEnd: 6 }
-				]
-			} );
-
-			const b = new AttributeElement( 'b' );
-			const newRange = writer.wrap( created.range, b );
-
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 1,
-				rangeEnd: 2,
-				children: [
-					{ instanceOf: Text, data: 'foo' },
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: DEFAULT_PRIORITY,
-						children: [
-							{ instanceOf: Text, data: 'bar' }
-						]
-					}
-				]
-			} );
+			test(
+				'<container:p>foo{bar}</container:p>',
+				'<attribute:b:1></attribute:b:1>',
+				'<container:p>foo[<attribute:b:1>bar</attribute:b:1>]</container:p>'
+			);
 		} );
 
 		it( 'should not wrap inside nested containers', () => {
-			// <div>[{foobar}<p>{baz}</p>]</div>
-			// wrap with <b>
-			// <div>[<b>{foobar}</b><p>{baz}</p>]</div>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'div',
-				rangeStart: 0,
-				rangeEnd: 2,
-				children: [
-					{ instanceOf: Text, data: 'foobar' },
-					{
-						instanceOf: ContainerElement,
-						name: 'p',
-						children: [
-							{ instanceOf: Text, data: 'baz' }
-						]
-					}
-				]
-			} );
-
-			const newRange = writer.wrap( created.range, new AttributeElement( 'b' ) );
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'div',
-				rangeStart: 0,
-				rangeEnd: 2,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: DEFAULT_PRIORITY,
-						children: [
-							{ instanceOf: Text, data: 'foobar' }
-						]
-					},
-					{
-						instanceOf: ContainerElement,
-						name: 'p',
-						children: [
-							{ instanceOf: Text, data: 'baz' }
-						]
-					}
-				]
-			} );
+			test(
+				'<container:div>[foobar<container:p>baz</container:p>]</container:div>',
+				'<attribute:b:1></attribute:b:1>',
+				'<container:div>[<attribute:b:1>foobar</attribute:b:1><container:p>baz</container:p>]</container:div>'
+			);
 		} );
 
 		it( 'wraps according to priorities', () => {
-			// <p>[<u>{foobar}</u>]</p>
-			// wrap with <b> that has higher priority than <u>
-			// <p>[<u><b>{foobar}</b></u>]</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'u',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'foobar' }
-						]
-					}
-				]
-			} );
-
-			const b = new AttributeElement( 'b' );
-			b.priority = 2;
-			const newRange = writer.wrap( created.range, b );
-
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'u',
-						priority: 1,
-						children: [
-							{
-								instanceOf: AttributeElement,
-								name: 'b',
-								priority: 2,
-								children: [
-									{ instanceOf: Text, data: 'foobar' }
-								]
-							}
-						]
-					}
-				]
-			} );
+			test(
+				'<container:p>[<attribute:u:1>foobar</attribute:u:1>]</container:p>',
+				'<attribute:b:2></attribute:b:2>',
+				'<container:p>[<attribute:u:1><attribute:b:2>foobar</attribute:b:2></attribute:u:1>]</container:p>'
+			);
 		} );
 
 		it( 'merges wrapped nodes #1', () => {
-			// <p>[<b>{foo}</b>{bar}<b>{baz}</b>]</p>
-			// wrap with <b>
-			// <p>[<b>{foobarbaz}</b>]</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 3,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: DEFAULT_PRIORITY,
-						children: [
-							{ instanceOf: Text, data: 'foo' }
-						]
-					},
-					{ instanceOf: Text, data: 'bar' },
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: DEFAULT_PRIORITY,
-						children: [
-							{ instanceOf: Text, data: 'baz' }
-						]
-					}
-				]
-			} );
-
-			const b = new AttributeElement( 'b' );
-			const newRange = writer.wrap( created.range, b );
-
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: DEFAULT_PRIORITY,
-						children: [
-							{ instanceOf: Text, data: 'foobarbaz' }
-						]
-					}
-				]
-			} );
+			test(
+				'<container:p>[<attribute:b:1>foo</attribute:b:1>bar<attribute:b:1>baz</attribute:b:1>]</container:p>',
+				'<attribute:b:1></attribute:b:1>',
+				'<container:p>[<attribute:b:1>foobarbaz</attribute:b:1>]</container:p>'
+			);
 		} );
 
 		it( 'merges wrapped nodes #2', () => {
-			// <p><b>{foo}</b>[{bar]baz}</p>
-			// wrap with <b>
-			// <p><b>{foo[bar}</b>]{baz}</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: DEFAULT_PRIORITY,
-						children: [
-							{ instanceOf: Text, data: 'foo' }
-						]
-					},
-					{ instanceOf: Text, data: 'barbaz', rangeEnd: 3 }
-				]
-			} );
-
-			const newRange = writer.wrap( created.range, new AttributeElement( 'b' ) );
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeEnd: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: DEFAULT_PRIORITY,
-						children: [
-							{ instanceOf: Text, data: 'foobar', rangeStart: 3 }
-						]
-					},
-					{ instanceOf: Text, data: 'baz' }
-				]
-			} );
+			test(
+				'<container:p><attribute:b:1>foo</attribute:b:1>[bar}baz</container:p>',
+				'<attribute:b:1></attribute:b:1>',
+				'<container:p><attribute:b:1>foo{bar</attribute:b:1>]baz</container:p>'
+			);
 		} );
 
 		it( 'merges wrapped nodes #3', () => {
-			// <p><b>{foobar}</b>[{baz}]</p>
-			// wrap with <b>
-			// <p><b>{foobar[baz}</b>]</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 1,
-				rangeEnd: 2,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: DEFAULT_PRIORITY,
-						children: [
-							{ instanceOf: Text, data: 'foobar' }
-						]
-					},
-					{ instanceOf: Text, data: 'baz', rangeEnd: 3 }
-				]
-			} );
-
-			const newRange = writer.wrap( created.range, new AttributeElement( 'b' ) );
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeEnd: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: DEFAULT_PRIORITY,
-						children: [
-							{ instanceOf: Text, data: 'foobarbaz', rangeStart: 6 }
-						]
-					}
-				]
-			} );
+			test(
+				'<container:p><attribute:b:1>foobar</attribute:b:1>[baz]</container:p>',
+				'<attribute:b:1></attribute:b:1>',
+				'<container:p><attribute:b:1>foobar{baz</attribute:b:1>]</container:p>'
+			);
 		} );
 
 		it( 'merges wrapped nodes #4', () => {
-			// <p>[{foo}<i>{bar}</i>]{baz}</p>
-			// wrap with <b>
-			// <p>[<b>{foo}<i>{bar}</i></b>]{baz}</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 2,
-				children: [
-					{ instanceOf: Text, data: 'foo' },
-					{
-						instanceOf: AttributeElement,
-						name: 'i',
-						priority: DEFAULT_PRIORITY,
-						children: [
-							{ instanceOf: Text, data: 'bar' }
-						]
-					},
-					{ instanceOf: Text, data: 'baz' }
-				]
-			} );
-
-			const newRange = writer.wrap( created.range, new AttributeElement( 'b' ) );
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: DEFAULT_PRIORITY,
-						children: [
-							{ instanceOf: Text, data: 'foo' },
-							{
-								instanceOf: AttributeElement,
-								name: 'i',
-								priority: DEFAULT_PRIORITY,
-								children: [
-									{ instanceOf: Text, data: 'bar' }
-								]
-							}
-						]
-					},
-					{ instanceOf: Text, data: 'baz' }
-				]
-			} );
+			test(
+				'<container:p>[foo<attribute:i:1>bar</attribute:i:1>]baz</container:p>',
+				'<attribute:b:1></attribute:b:1>',
+				'<container:p>[<attribute:b:1>foo<attribute:i:1>bar</attribute:i:1></attribute:b:1>]baz</container:p>'
+			);
 		} );
 
 		it( 'merges wrapped nodes #5', () => {
-			// <p>[{foo}<i>{bar}</i>{baz}]</p>
-			// wrap with <b>, that has higher priority than <i>
-			// <p>[<b>{foo}</b><i><b>{bar}</b></i><b>{baz}</b>]</p>
-			const created = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 3,
-				children: [
-					{ instanceOf: Text, data: 'foo' },
-					{
-						instanceOf: AttributeElement,
-						name: 'i',
-						priority: 1,
-						children: [
-							{ instanceOf: Text, data: 'bar' }
-						]
-					},
-					{ instanceOf: Text, data: 'baz' }
-				]
-			} );
-
-			const b = new AttributeElement( 'b' );
-			b.priority = 2;
-			const newRange = writer.wrap( created.range, b );
-			test( writer, newRange, created.node, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 3,
-				children: [
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 2,
-						children: [
-							{ instanceOf: Text, data: 'foo' }
-						]
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'i',
-						priority: 1,
-						children: [
-							{
-								instanceOf: AttributeElement,
-								name: 'b',
-								priority: 2,
-								children: [
-									{ instanceOf: Text, data: 'bar' }
-								]
-							}
-						]
-					},
-					{
-						instanceOf: AttributeElement,
-						name: 'b',
-						priority: 2,
-						children: [
-							{ instanceOf: Text, data: 'baz' }
-						]
-					}
-				]
-			} );
+			test(
+				'<container:p>[foo<attribute:i:1>bar</attribute:i:1>baz]</container:p>',
+				'<attribute:b:2></attribute:b:2>',
+				'<container:p>' +
+				'[' +
+					'<attribute:b:2>foo</attribute:b:2>' +
+					'<attribute:i:1>' +
+						'<attribute:b:2>bar</attribute:b:2>' +
+					'</attribute:i:1>' +
+					'<attribute:b:2>baz</attribute:b:2>' +
+				']' +
+				'</container:p>'
+			);
 		} );
 
 		it( 'should wrap single element by merging attributes', () => {
-			// <p>[<b foo="bar" one="two"></b>]</p>
-			// wrap with <b baz="qux" one="two"></b>
-			// <p>[<b foo="bar" one="two" baz="qux"></b>]</p>
-			const b = new AttributeElement( 'b', {
-				foo: 'bar',
-				one: 'two'
-			} );
-			const p = new ContainerElement( 'p', null, b );
-			const range = Range.createFromParentsAndOffsets( p, 0, p, 1 );
-			const wrapper = new AttributeElement( 'b', {
-				baz: 'qux',
-				one: 'two'
-			} );
-
-			const newRange = writer.wrap( range, wrapper );
-			expect( b.getAttribute( 'foo' ) ).to.equal( 'bar' );
-			expect( b.getAttribute( 'baz' ) ).to.equal( 'qux' );
-			expect( b.getAttribute( 'one' ) ).to.equal( 'two' );
-
-			test( writer, newRange, p, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{ instanceOf: AttributeElement, name: 'b', children: [] }
-				]
-			} );
+			test(
+				'<container:p>[<attribute:b:1 foo="bar" one="two"></attribute:b:1>]</container:p>',
+				'<attribute:b:1 baz="qux" one="two"></attribute:b:1>',
+				'<container:p>[<attribute:b:1 foo="bar" one="two" baz="qux"></attribute:b:1>]</container:p>'
+			);
 		} );
 
 		it( 'should not merge attributes when they differ', () => {
-			// <p>[<b foo="bar" ></b>]</p>
-			// wrap with <b foo="baz"></b>
-			// <p>[<b foo="baz"><b foo="bar"></b></b>]</p>
-			const b = new AttributeElement( 'b', {
-				foo: 'bar'
-			} );
-			const p = new ContainerElement( 'p', null, b );
-			const range = Range.createFromParentsAndOffsets( p, 0, p, 1 );
-			const wrapper = new AttributeElement( 'b', {
-				foo: 'baz'
-			} );
-
-			const newRange = writer.wrap( range, wrapper );
-			expect( b.getAttribute( 'foo' ) ).to.equal( 'bar' );
-			expect( b.parent.isSimilar( wrapper ) ).to.be.true;
-			expect( b.parent.getAttribute( 'foo' ) ).to.equal( 'baz' );
-
-			test( writer, newRange, p, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{ instanceOf: AttributeElement, name: 'b', children: [
-						{ instanceOf: AttributeElement, name: 'b', children: [] }
-					] }
-				]
-			} );
+			test(
+				'<container:p>[<attribute:b:1 foo="bar"></attribute:b:1>]</container:p>',
+				'<attribute:b:1 foo="baz"></attribute:b:1>',
+				'<container:p>[<attribute:b:1 foo="baz"><attribute:b:1 foo="bar"></attribute:b:1></attribute:b:1>]</container:p>'
+			);
 		} );
 
 		it( 'should wrap single element by merging classes', () => {
-			// <p>[<b class="foo bar baz" ></b>]</p>
-			// wrap with <b class="foo bar qux jax"></b>
-			// <p>[<b class="foo bar baz qux jax"></b>]</p>
-			const b = new AttributeElement( 'b', {
-				class: 'foo bar baz'
-			} );
-			const p = new ContainerElement( 'p', null, b );
-			const range = Range.createFromParentsAndOffsets( p, 0, p, 1 );
-			const wrapper = new AttributeElement( 'b', {
-				class: 'foo bar qux jax'
-			} );
-
-			const newRange = writer.wrap( range, wrapper );
-			expect( b.hasClass( 'foo', 'bar', 'baz', 'qux', 'jax' ) ).to.be.true;
-			test( writer, newRange, p, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{ instanceOf: AttributeElement, name: 'b', children: [] }
-				]
-			} );
+			test(
+				'<container:p>[<attribute:b:1 class="foo bar baz"></attribute:b:1>]</container:p>',
+				'<attribute:b:1 class="foo bar qux jax"></attribute:b:1>',
+				'<container:p>[<attribute:b:1 class="foo bar baz qux jax"></attribute:b:1>]</container:p>'
+			);
 		} );
 
 		it( 'should wrap single element by merging styles', () => {
-			// <p>[<b style="color:red; position: absolute;"></b>]</p>
-			// wrap with <b style="color:red; top: 20px;"></b>
-			// <p>[<b class="color:red; position: absolute; top:20px;"></b>]</p>
-			const b = new AttributeElement( 'b', {
-				style: 'color: red; position: absolute;'
-			} );
-			const p = new ContainerElement( 'p', null, b );
-			const range = Range.createFromParentsAndOffsets( p, 0, p, 1 );
-			const wrapper = new AttributeElement( 'b', {
-				style: 'color:red; top: 20px;'
-			} );
-
-			const newRange = writer.wrap( range, wrapper );
-			expect( b.getStyle( 'color' ) ).to.equal( 'red' );
-			expect( b.getStyle( 'position' ) ).to.equal( 'absolute' );
-			expect( b.getStyle( 'top' ) ).to.equal( '20px' );
-
-			test( writer, newRange, p, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{ instanceOf: AttributeElement, name: 'b', children: [] }
-				]
-			} );
+			test(
+				'<container:p>[<attribute:b:1 style="color:red; position: absolute;"></attribute:b:1>]</container:p>',
+				'<attribute:b:1 style="color:red; top: 20px;"></attribute:b:1>',
+				'<container:p>[<attribute:b:1 style="color:red;position:absolute;top:20px;"></attribute:b:1>]</container:p>'
+			);
 		} );
 
 		it( 'should not merge styles when they differ', () => {
-			// <p>[<b style="color:red;"></b>]</p>
-			// wrap with <b style="color:black;"></b>
-			// <p>[<b style="color:black;"><b style="color:red;"></b></b>]</p>
-			const b = new AttributeElement( 'b', {
-				style: 'color:red'
-			} );
-			const p = new ContainerElement( 'p', null, b );
-			const range = Range.createFromParentsAndOffsets( p, 0, p, 1 );
-			const wrapper = new AttributeElement( 'b', {
-				style: 'color:black'
-			} );
-
-			const newRange = writer.wrap( range, wrapper );
-			expect( b.getStyle( 'color' ) ).to.equal( 'red' );
-			expect( b.parent.isSimilar( wrapper ) ).to.be.true;
-			expect( b.parent.getStyle( 'color' ) ).to.equal( 'black' );
-
-			test( writer, newRange, p, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{ instanceOf: AttributeElement, name: 'b', children: [
-						{ instanceOf: AttributeElement, name: 'b', children: [] }
-					] }
-				]
-			} );
+			test(
+				'<container:p>[<attribute:b:1 style="color:red;"></attribute:b:1>]</container:p>',
+				'<attribute:b:1 style="color:black;"></attribute:b:1>',
+				'<container:p>' +
+				'[' +
+					'<attribute:b:1 style="color:black;">' +
+						'<attribute:b:1 style="color:red;"></attribute:b:1>' +
+					'</attribute:b:1>' +
+				']' +
+				'</container:p>'
+			);
 		} );
 
 		it( 'should not merge single elements when they have different priority', () => {
-			// <p>[<b style="color:red;"></b>]</p>
-			// wrap with <b style="color:red;"></b> with different priority
-			// <p>[<b style="color:red;"><b style="color:red;"></b></b>]</p>
-			const b = new AttributeElement( 'b', {
-				style: 'color:red'
-			} );
-			const p = new ContainerElement( 'p', null, b );
-			const range = Range.createFromParentsAndOffsets( p, 0, p, 1 );
-			const wrapper = new AttributeElement( 'b', {
-				style: 'color:red'
-			} );
-			wrapper.priority = b.priority - 1;
-
-			const newRange = writer.wrap( range, wrapper );
-			expect( b.getStyle( 'color' ) ).to.equal( 'red' );
-			expect( b.parent.isSimilar( wrapper ) ).to.be.true;
-			expect( b.parent.getStyle( 'color' ) ).to.equal( 'red' );
-
-			test( writer, newRange, p, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{ instanceOf: AttributeElement, name: 'b', children: [
-						{ instanceOf: AttributeElement, name: 'b', children: [] }
-					] }
-				]
-			} );
+			test(
+				'<container:p>[<attribute:b:2 style="color:red;"></attribute:b:2>]</container:p>',
+				'<attribute:b:1 style="color:red;"></attribute:b:1>',
+				'<container:p>' +
+				'[' +
+					'<attribute:b:1 style="color:red;">' +
+						'<attribute:b:2 style="color:red;"></attribute:b:2>' +
+					'</attribute:b:1>' +
+				']</container:p>'
+			);
 		} );
 
 		it( 'should be merged with outside element when wrapping all children', () => {
-			// <p><b foo="bar">[{foobar}<i>{baz}</i>]</b></p>
-			// wrap with <b baz="qux"></b>
-			// <p>[<b foo="bar" baz="qux">{foobar}</b>]</p>
-			const text1 = new Text( 'foobar' );
-			const text2 = new Text( 'baz' );
-			const i = new AttributeElement( 'i', null, text2 );
-			const b = new AttributeElement( 'b', { foo: 'bar' }, [ text1, i ] );
-			const p = new ContainerElement( 'p', null, [ b ] );
-			const wrapper = new AttributeElement( 'b', { baz: 'qux' } );
-			const range = Range.createFromParentsAndOffsets( b, 0, b, 2 );
-
-			const newRange = writer.wrap( range, wrapper );
-			expect( b.getAttribute( 'foo' ) ).to.equal( 'bar' );
-			expect( b.getAttribute( 'baz' ) ).to.equal( 'qux' );
-			test( writer, newRange, p, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{
-						instanceof: AttributeElement,
-						name: 'b',
-						children: [
-							{ instanceOf: Text, data: 'foobar' },
-							{
-								instanceOf: AttributeElement,
-								name: 'i',
-								children: [
-									{ instanceOf: Text, data: 'baz' }
-								]
-							}
-						]
-					}
-				]
-			} );
+			test(
+				'<container:p><attribute:b:1 foo="bar">[foobar<attribute:i:1>baz</attribute:i:1>]</attribute:b:1></container:p>',
+				'<attribute:b:1 baz="qux"></attribute:b:1>',
+				'<container:p>' +
+				'[' +
+					'<attribute:b:1 foo="bar" baz="qux">' +
+						'foobar' +
+						'<attribute:i:1>baz</attribute:i:1>' +
+					'</attribute:b:1>' +
+				']' +
+				'</container:p>'
+			);
 		} );
 	} );
 } );

+ 59 - 258
packages/ckeditor5-engine/tests/treeview/writer/wrapposition.js

@@ -11,17 +11,26 @@ import Writer from '/ckeditor5/engine/treeview/writer.js';
 import Text from '/ckeditor5/engine/treeview/text.js';
 import Element from '/ckeditor5/engine/treeview/element.js';
 import ContainerElement from '/ckeditor5/engine/treeview/containerelement.js';
-import AttributeElement from '/ckeditor5/engine/treeview/attributeelement.js';
 import Position from '/ckeditor5/engine/treeview/position.js';
-import utils from '/tests/engine/treeview/writer/_utils/utils.js';
-import { DEFAULT_PRIORITY } from '/ckeditor5/engine/treeview/attributeelement.js';
 import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
+import { stringify, parse } from '/tests/engine/_utils/view.js';
 
 describe( 'wrapPosition', () => {
-	const create = utils.create;
-	const test = utils.test;
 	let writer;
 
+	/**
+	 * Executes test using `parse` and `stringify` utils functions.
+	 *
+	 * @param {String} input
+	 * @param {String} unwrapAttribute
+	 * @param {String} expected
+	 */
+	function test( input, unwrapAttribute, expected ) {
+		const { view, selection } = parse( input );
+		const newPosition = writer.wrapPosition( selection.getFirstPosition(), parse( unwrapAttribute ) );
+		expect( stringify( view, newPosition, { showType: true, showPriority: true } ) ).to.equal( expected );
+	}
+
 	beforeEach( () => {
 		writer = new Writer();
 	} );
@@ -37,278 +46,70 @@ describe( 'wrapPosition', () => {
 	} );
 
 	it( 'should wrap position at the beginning of text node', () => {
-		// <p>{|foobar}</p>
-		// wrap with <b>
-		// <p><b>|<b>{foobar}</p>
-		const description = {
-			instanceOf: ContainerElement,
-			name: 'p',
-			children: [
-				{ instanceOf: Text, data: 'foobar', position: 0 }
-			]
-		};
-		const created = create( writer, description );
-		const newPosition = writer.wrapPosition( created.position, new AttributeElement( 'b' ) );
-		test( writer, newPosition, created.node, {
-			instanceOf: ContainerElement,
-			name: 'p',
-			children: [
-				{ instanceOf: AttributeElement, name: 'b', position: 0 },
-				{ instanceOf: Text, data: 'foobar' }
-			]
-		} );
+		test(
+			'<container:p>{}foobar</container:p>',
+			'<attribute:b:1></attribute:b:1>',
+			'<container:p><attribute:b:1>[]</attribute:b:1>foobar</container:p>'
+		);
 	} );
 
 	it( 'should wrap position inside text node', () => {
-		// <p>{foo|bar}</p>
-		// wrap with <b>
-		// <p>{foo}<b>|</b>{bar}</p>
-		const description = {
-			instanceOf: ContainerElement,
-			name: 'p',
-			children: [
-				{ instanceOf: Text, data: 'foobar', position: 3 }
-			]
-		};
-		const created = create( writer, description );
-		const newPosition = writer.wrapPosition( created.position, new AttributeElement( 'b' ) );
-		test( writer, newPosition, created.node, {
-			instanceOf: ContainerElement,
-			name: 'p',
-			children: [
-				{ instanceOf: Text, data: 'foo' },
-				{ instanceOf: AttributeElement, name: 'b', position: 0 },
-				{ instanceOf: Text, data: 'bar' }
-			]
-		} );
+		test(
+			'<container:p>foo{}bar</container:p>',
+			'<attribute:b:1></attribute:b:1>',
+			'<container:p>foo<attribute:b:1>[]</attribute:b:1>bar</container:p>'
+		);
 	} );
 
 	it( 'should wrap position at the end of text node', () => {
-		// <p>{foobar|}</p>
-		// wrap with <b>
-		// <p>{foobar}<b>|</b></p>
-		const description = {
-			instanceOf: ContainerElement,
-			name: 'p',
-			children: [
-				{ instanceOf: Text, data: 'foobar', position: 6 }
-			]
-		};
-		const created = create( writer, description );
-		const newPosition = writer.wrapPosition( created.position, new AttributeElement( 'b' ) );
-		test( writer, newPosition, created.node, {
-			instanceOf: ContainerElement,
-			name: 'p',
-			children: [
-				{ instanceOf: Text, data: 'foobar' },
-				{ instanceOf: AttributeElement, name: 'b', position: 0 }
-			]
-		} );
+		test(
+			'<container:p>foobar{}</container:p>',
+			'<attribute:b:1></attribute:b:1>',
+			'<container:p>foobar<attribute:b:1>[]</attribute:b:1></container:p>'
+		);
 	} );
 
 	it( 'should merge with existing attributes #1', () => {
-		// <p><b>{foo}</b>|</p>
-		// wrap with <b>
-		// <p><b>{foo|}</b></p>
-		const description = {
-			instanceOf: ContainerElement,
-			name: 'p',
-			position: 1,
-			children: [
-				{
-					instanceOf: AttributeElement,
-					name: 'b',
-					children: [
-						{ instanceOf: Text, data: 'foobar' }
-					]
-				}
-			]
-		};
-
-		const created = create( writer, description );
-		const newPosition = writer.wrapPosition( created.position, new AttributeElement( 'b' ) );
-
-		test( writer, newPosition, created.node, {
-			instanceOf: ContainerElement,
-			name: 'p',
-			children: [
-				{
-					instanceOf: AttributeElement,
-					name: 'b',
-					priority: DEFAULT_PRIORITY,
-					children: [
-						{ instanceOf: Text, data: 'foobar', position: 6 }
-					]
-				}
-			]
-		} );
+		test(
+			'<container:p><attribute:b:1>foo</attribute:b:1>[]</container:p>',
+			'<attribute:b:1></attribute:b:1>',
+			'<container:p><attribute:b:1>foo{}</attribute:b:1></container:p>'
+		);
 	} );
 
 	it( 'should merge with existing attributes #2', () => {
-		// <p>|<b>{foo}</b></p>
-		// wrap with <b>
-		// <p><b>{|foo}</b></p>
-		const description = {
-			instanceOf: ContainerElement,
-			name: 'p',
-			position: 0,
-			children: [
-				{
-					instanceOf: AttributeElement,
-					name: 'b',
-					children: [
-						{ instanceOf: Text, data: 'foobar' }
-					]
-				}
-			]
-		};
-
-		const created = create( writer, description );
-		const newPosition = writer.wrapPosition( created.position, new AttributeElement( 'b' ) );
-
-		test( writer, newPosition, created.node, {
-			instanceOf: ContainerElement,
-			name: 'p',
-			children: [
-				{
-					instanceOf: AttributeElement,
-					name: 'b',
-					priority: DEFAULT_PRIORITY,
-					children: [
-						{ instanceOf: Text, data: 'foobar', position: 0 }
-					]
-				}
-			]
-		} );
+		test(
+			'<container:p>[]<attribute:b:1>foo</attribute:b:1></container:p>',
+			'<attribute:b:1></attribute:b:1>',
+			'<container:p><attribute:b:1>{}foo</attribute:b:1></container:p>'
+		);
 	} );
 
 	it( 'should wrap when inside nested attributes', () => {
-		// <p><b>{foo|bar}</b></p>
-		// wrap with <u>
-		// <p><b>{foo}</b><u><b>|</b></u><b>{bar}</b></p>
-		const description = {
-			instanceOf: ContainerElement,
-			name: 'p',
-			children: [
-				{
-					instanceOf: AttributeElement,
-					name: 'b',
-					children: [
-						{ instanceOf: Text, data: 'foobar', position: 3 }
-					]
-				}
-			]
-		};
-
-		const created = create( writer, description );
-		const newPosition = writer.wrapPosition( created.position, new AttributeElement( 'u' ) );
-		test( writer, newPosition, created.node, {
-			instanceOf: ContainerElement,
-			name: 'p',
-			children: [
-				{
-					instanceOf: AttributeElement,
-					name: 'b',
-					children: [
-						{ instanceOf: Text, data: 'foo' }
-					]
-				},
-				{
-					instanceOf: AttributeElement,
-					name: 'u',
-					children: [
-						{ instanceOf: AttributeElement, name: 'b', children: [] }
-					]
-				},
-				{
-					instanceOf: AttributeElement,
-					name: 'b',
-					children: [
-						{ instanceOf: Text, data: 'bar' }
-					]
-				}
-			]
-		}  );
+		test(
+			'<container:p><attribute:b:1>foo{}bar</attribute:b:1></container:p>',
+			'<attribute:u:1></attribute:u:1>',
+			'<container:p>' +
+				'<attribute:b:1>foo</attribute:b:1>' +
+				'<attribute:u:1><attribute:b:1>[]</attribute:b:1></attribute:u:1>' +
+				'<attribute:b:1>bar</attribute:b:1>' +
+			'</container:p>'
+		);
 	} );
 
 	it( 'should merge when wrapping between same attribute', () => {
-		// <p><b>{foo}</b>|<b>{bar}</b></p>
-		// wrap with <b>
-		// <p><b>{foo|bar}</b></p>
-		const description = {
-			instanceOf: ContainerElement,
-			name: 'p',
-			position: 1,
-			children: [
-				{
-					instanceOf: AttributeElement,
-					name: 'b',
-					children: [
-						{ instanceOf: Text, data: 'foo' }
-					]
-				},
-				{
-					instanceOf: AttributeElement,
-					name: 'b',
-					children: [
-						{ instanceOf: Text, data: 'bar' }
-					]
-				}
-			]
-		};
-
-		const created = create( writer, description );
-		const newPosition = writer.wrapPosition( created.position, new AttributeElement( 'b' ) );
-
-		test( writer, newPosition, created.node, {
-			instanceOf: ContainerElement,
-			name: 'p',
-			children: [
-				{
-					instanceOf: AttributeElement,
-					name: 'b',
-					children: [
-						{ instanceOf: Text, data: 'foobar', position: 3 }
-					]
-				}
-			]
-		}  );
+		test(
+			'<container:p><attribute:b:1>foo</attribute:b:1>[]<attribute:b:1>bar</attribute:b:1></container:p>',
+			'<attribute:b:1></attribute:b:1>',
+			'<container:p><attribute:b:1>foo{}bar</attribute:b:1></container:p>'
+		);
 	} );
 
-	it( 'should return same position when inside same attribute', () => {
-		// <p><b>{foobar}|</b></p>
-		// wrap with <b>
-		// <p><b>{foobar|}</b></p>
-		const description = {
-			instanceOf: ContainerElement,
-			name: 'p',
-			children: [
-				{
-					instanceOf: AttributeElement,
-					name: 'b',
-					position: 1,
-					children: [
-						{ instanceOf: Text, data: 'foobar' }
-					]
-				}
-			]
-		};
-
-		const created = create( writer, description );
-		const newPosition = writer.wrapPosition( created.position, new AttributeElement( 'b' ) );
-
-		test( writer, newPosition, created.node, {
-			instanceOf: ContainerElement,
-			name: 'p',
-			children: [
-				{
-					instanceOf: AttributeElement,
-					name: 'b',
-					children: [
-						{ instanceOf: Text, data: 'foobar', position: 6 }
-					]
-				}
-			]
-		} );
+	it( 'should move position to text node if in same attribute', () => {
+		test(
+			'<container:p><attribute:b:1>foobar[]</attribute:b:1></container:p>',
+			'<attribute:b:1></attribute:b:1>',
+			'<container:p><attribute:b:1>foobar{}</attribute:b:1></container:p>'
+		);
 	} );
 } );

+ 8 - 22
packages/ckeditor5-engine/tests/treeview/writer/writer.js

@@ -12,10 +12,9 @@ import ContainerElement from '/ckeditor5/engine/treeview/containerelement.js';
 import AttributeElement from '/ckeditor5/engine/treeview/attributeelement.js';
 import Text from '/ckeditor5/engine/treeview/text.js';
 import Position from '/ckeditor5/engine/treeview/position.js';
-import utils from '/tests/engine/treeview/writer/_utils/utils.js';
+import { parse } from '/tests/engine/_utils/view.js';
 
 describe( 'Writer', () => {
-	const create = utils.create;
 	let writer;
 
 	beforeEach( () => {
@@ -47,33 +46,20 @@ describe( 'Writer', () => {
 
 	describe( 'move', () => {
 		it( 'should move nodes using remove and insert methods', () => {
-			// <p>[{foobar}]</p>
-			// Move to <div>|</div>
-			// <div>[{foobar}]</div>
-			const source = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'p',
-				rangeStart: 0,
-				rangeEnd: 1,
-				children: [
-					{ instanceOf: Text, data: 'foobar' }
-				]
-			} );
-			const target = create( writer, {
-				instanceOf: ContainerElement,
-				name: 'div',
-				position: 0
-			} );
+			const { selection: sourceSelection } = parse( '<container:p>[foobar]</container:p>' );
+			const { selection: targetSelection } = parse( '<container:div>[]</container:div>' );
 
 			const removeSpy = sinon.spy( writer, 'remove' );
 			const insertSpy = sinon.spy( writer, 'insert' );
+			const sourceRange = sourceSelection.getFirstRange();
+			const targetPosition = targetSelection.getFirstPosition();
 
-			const newRange = writer.move( source.range, target.position );
+			const newRange = writer.move( sourceRange, targetPosition );
 
 			sinon.assert.calledOnce( removeSpy );
-			sinon.assert.calledWithExactly( removeSpy, source.range );
+			sinon.assert.calledWithExactly( removeSpy, sourceRange );
 			sinon.assert.calledOnce( insertSpy );
-			sinon.assert.calledWithExactly( insertSpy, target.position, removeSpy.firstCall.returnValue );
+			sinon.assert.calledWithExactly( insertSpy, targetPosition, removeSpy.firstCall.returnValue );
 			expect( newRange ).to.equal( insertSpy.firstCall.returnValue );
 		} );
 	} );