Browse Source

Added tests to core.treeView.Writer#breakAttributes.

Szymon Kupś 9 years ago
parent
commit
d7e21f5c4f

+ 56 - 23
packages/ckeditor5-engine/src/treeview/writer.js

@@ -6,10 +6,14 @@
 'use strict';
 
 import Position from './position.js';
+import Element from './element.js';
+import Text from './text.js';
 import utils from '../utils.js';
 
 /**
- * @class treeView.Writer
+ * Tree model Writer class.
+ *
+ * @memberOf core.treeModel
  */
  export default class Writer {
 	constructor() {
@@ -26,27 +30,31 @@ import utils from '../utils.js';
 	/**
 	 * Returns true if provided node is a container node.
 	 *
-	 * @param {treeView.Node} node
+	 * @param {core.treeView.Element} node
 	 * @returns {Boolean}
      */
 	isContainer( node ) {
-		return !this._priorities.has( node );
+		const isElement = node instanceof Element;
+
+		return isElement && !this._priorities.has( node );
 	}
 
 	/**
 	 * Returns true if provided node is an attribute node.
 	 *
-	 * @param {treeView.Node} node
+	 * @param {core.treeView.Element} node
 	 * @returns {Boolean}
 	 */
 	isAttribute( node ) {
-		return this._priorities.has( node );
+		const isElement = node instanceof Element;
+
+		return isElement && this._priorities.has( node );
 	}
 
 	/**
 	 * Sets node priority.
 	 *
-	 * @param {treeView.Node} node
+	 * @param {core.treeView.Node} node
 	 * @param {Number} priority
      */
 	setPriority( node, priority ) {
@@ -56,7 +64,7 @@ import utils from '../utils.js';
 	/**
 	 * Returns node's priority, undefined if node's priority cannot be found.
 	 *
-	 * @param {treeView.Node} node
+	 * @param {core.treeView.Node} node
 	 * @returns {Number|undefined}
      */
 	getPriority( node ) {
@@ -77,13 +85,23 @@ import utils from '../utils.js';
 		this.mergeAttributes( insertionPosition );
 	}
 
-	// return position
+	/**
+	 * Breaks attributes at provided position. Returns new position.
+	 * Examples:
+	 *        <p>foo<b><u>bar|</u></b></p> -> <p>foo<b><u>bar</u></b>|</p>
+	 *        <p>foo<b><u>|bar</u></b></p> -> <p>foo|<b><u>bar</u></b></p>
+	 *        <p>foo<b><u>b|ar</u></b></p> -> <p>foo<b><u>b</u></b>|<b><u>ar</u></b></p>
+	 *
+	 * @param {core.treeView.Position} position
+	 * @returns {core.treeView.Position}
+	 */
 	breakAttributes( position ) {
-		let positionOffset = position.offset;
-		let positionParent = position.parent;
+		const positionOffset = position.offset;
+		const positionParent = position.parent;
 
+		// Position's parent is container, so no attributes to break.
 		if ( this.isContainer( positionParent ) ) {
-			return position;
+			return Position.createFromPosition( position );
 		}
 
 		const parentIsText = positionParent instanceof Text;
@@ -93,46 +111,61 @@ import utils from '../utils.js';
 		// <p>foo<b><u>bar</u>|</b></p>
 		// <p>foo<b><u>bar</u></b>|</p>
 		if ( positionOffset == length ) {
-			const parentPosition = new Position( positionParent.parent, positionParent.getIndex() + 1 );
+			const newPosition = new Position( positionParent.parent, positionParent.getIndex() + 1 );
 
-			return this.breakAttributes( parentPosition );
+			return this.breakAttributes( newPosition );
 		} else
 		// <p>foo<b><u>|bar</u></b></p>
 		// <p>foo<b>|<u>bar</u></b></p>
 		// <p>foo|<b><u>bar</u></b></p>
 		if ( positionOffset === 0 ) {
-			const parentPosition = new Position( positionParent.parent, positionParent.getIndex() );
+			const newPosition = new Position( positionParent.parent, positionParent.getIndex() );
 
-			return this.breakAttributes( parentPosition );
+			return this.breakAttributes( newPosition );
 		}
 		// <p>foo<b><u>"b|ar"</u></b></p>
 		// <p>foo<b><u>"b"|"ar"</u></b></p>
-		// <p>foo<b><u>b</u>|</u>ar</u></b></p>
+		// <p>foo<b><u>b</u>|<u>ar</u></b></p>
 		// <p>foo<b><u>b</u></b>|<b><u>ar</u></b></p>
 		else {
-			// Break.
 			const offsetAfter = positionParent.getIndex() + 1;
 
 			if ( parentIsText ) {
 				// Break text.
+				// Get part of the text that need to be moved.
 				const textToMove = positionParent.data.slice( positionOffset );
+
+				// Leave rest of the text in position's parent.
 				positionParent.data = positionParent.data.slice( 0, positionOffset );
+
+				// Insert new text node after position's parent text node.
 				positionParent.parent.insertChildren( offsetAfter, new Text( textToMove ) );
 
-				return new Position( positionParent.parent, offsetAfter );
+				// Create new position to work on.
+				const newPosition = new Position( positionParent.parent, offsetAfter );
+
+				return this.breakAttributes( newPosition );
 			} else {
 				// Break element.
-				const nodeClone = positionParent.cloneNode();
+				const nodeClone = positionParent.clone();
+
 				// Clone priority.
-				this._priorities.set( nodeClone, this._priorities.get( positionParent ) );
+				this.setPriority( nodeClone, this.getPriority( positionParent ) );
 
+				// Insert cloned node to position's parent node.
 				positionParent.parent.insertChildren( offsetAfter, nodeClone );
 
-				//const nodesToMove = sourceElement.removeChildren( sourceOffset, this.howMany );
+				// Get nodes to move.
+				const count = positionParent.getChildCount() - positionOffset;
+				const nodesToMove = positionParent.removeChildren( positionOffset, count );
+
+				// Move nodes to cloned node.
+				nodeClone.appendChildren( nodesToMove );
 
-				//nodeClone.appendChildren( nodesToMove );
+				// Create new position to work on.
+				const newPosition = new Position( positionParent.parent, offsetAfter );
 
-				return new Position( positionParent.parent, offsetAfter );
+				return this.breakAttributes( newPosition );
 			}
 		}
 	}

+ 191 - 12
packages/ckeditor5-engine/tests/treeview/writer.js

@@ -8,31 +8,34 @@
 'use strict';
 
 import Writer from '/ckeditor5/core/treeview/writer.js';
+import Element from '/ckeditor5/core/treeview/element.js';
+import Text from '/ckeditor5/core/treeview/text.js';
+import Position from '/ckeditor5/core/treeview/position.js';
 
 describe( 'Writer', () => {
 	describe( 'isContainer', () => {
-		it( 'should return true for container nodes', () => {
-			const mockContainerNode = {};
-			const mockAttributeNode = {};
+		it( 'should return true for container elements', () => {
+			const containerElement = new Element( 'p' );
+			const attributeElement = new Element( 'b' );
 			const writer = new Writer();
 
-			writer._priorities.set( mockAttributeNode, 1 );
+			writer._priorities.set( attributeElement, 1 );
 
-			expect( writer.isContainer( mockContainerNode ) ).to.be.true;
-			expect( writer.isContainer( mockAttributeNode ) ).to.be.false;
+			expect( writer.isContainer( containerElement ) ).to.be.true;
+			expect( writer.isContainer( attributeElement ) ).to.be.false;
 		} );
 	} );
 
 	describe( 'isAttribute', () => {
-		it( 'should return true for container nodes', () => {
-			const mockContainerNode = {};
-			const mockAttributeNode = {};
+		it( 'should return true for container elements', () => {
+			const containerElement = new Element( 'p' );
+			const attributeElement = new Element( 'b' );
 			const writer = new Writer();
 
-			writer._priorities.set( mockAttributeNode, 1 );
+			writer._priorities.set( attributeElement, 1 );
 
-			expect( writer.isAttribute( mockContainerNode ) ).to.be.false;
-			expect( writer.isAttribute( mockAttributeNode ) ).to.be.true;
+			expect( writer.isAttribute( containerElement ) ).to.be.false;
+			expect( writer.isAttribute( attributeElement ) ).to.be.true;
 		} );
 	} );
 
@@ -55,4 +58,180 @@ describe( 'Writer', () => {
 			expect( writer.getPriority( nodeMock ) ).to.equal( 12 );
 		} );
 	} );
+
+	describe( 'breakAttributes', () => {
+		// Start position is at the begin of the text node.
+		// After breaking it should be at the begin of the 'p' element.
+		it( '<p>|foobar</p>', () => {
+			const writer = new Writer();
+			const text = new Text( 'foobar' );
+			const element = new Element( 'p', {}, [ text ] );
+			const position = new Position( text, 0 );
+
+			const newPosition = writer.breakAttributes( position );
+			const newParent = newPosition.parent;
+
+			expect( newPosition.offset ).to.equal( 0 );
+			expect( newParent ).to.equal( element );
+			expect( newParent.getChildCount() ).to.equal( 1 );
+			expect( newParent.getChild( 0 ) ).to.equal( text );
+		} );
+
+		// Start position is located in the middle of text node.
+		// After breaking it should be located inside 'p' element, between two text nodes 'foo' and 'bar'.
+		it( '<p>foo|bar</p>', () => {
+			const writer = new Writer();
+			const text = new Text( 'foobar' );
+			const element = new Element( 'p', {}, [ text ] );
+			const position = new Position( text, 3 );
+
+			const newPosition = writer.breakAttributes( position );
+			const newParent = newPosition.parent;
+
+			expect( newPosition.offset ).to.equal( 1 );
+			expect( newParent ).to.equal( element );
+			expect( newParent.getChildCount() ).to.equal( 2 );
+			expect( newParent.getChild( 0 ) ).to.be.instanceOf( Text );
+			expect( newParent.getChild( 1 ) ).to.be.instanceOf( Text );
+			expect( newParent.getChild( 0 ).data ).to.equal( 'foo' );
+			expect( newParent.getChild( 1 ).data ).to.equal( 'bar' );
+		} );
+
+		// Start position is at the end of the text node.
+		// After breaking it should be at the end of the 'p' element.
+		it( '<p>foobar|</p>', () => {
+			const writer = new Writer();
+			const text = new Text( 'foobar' );
+			const element = new Element( 'p', {}, [ text ] );
+			const position = new Position( text, 6 );
+
+			const newPosition = writer.breakAttributes( position );
+			const newParent = newPosition.parent;
+
+			expect( newPosition.offset ).to.equal( 1 );
+			expect( newParent ).to.equal( element );
+			expect( newParent.getChildCount() ).to.equal( 1 );
+			expect( newParent.getChild( 0 ) ).to.equal( text );
+		} );
+
+		// <p><b>foo|bar</b></p> -> <p><b>foo</b>|<b>bar</b></p>
+		it( '<p><b>foo|bar</b></p>', () => {
+			const writer = new Writer();
+			const text = new Text( 'foobar' );
+			const b = new Element( 'b', {}, [ text ] );
+			const p = new Element( 'p', {}, [ b ] );
+			const position = new Position( text, 3 );
+			writer.setPriority( b, 1 );
+
+			const newPosition = writer.breakAttributes( position );
+			const parent = newPosition.parent;
+
+			expect( parent ).to.equal( p );
+			expect( newPosition.offset ).to.equal( 1 );
+			expect( p.getChildCount() ).to.equal( 2 );
+			const child1 = p.getChild( 0 );
+			const child2 = p.getChild( 1 );
+			expect( child1 ).to.be.instanceOf( Element );
+			expect( child1.name ).to.equal( 'b' );
+			expect( child1.getChildCount() ).to.equal( 1 );
+			expect( child1.getChild( 0 ).data ).to.equal( 'foo' );
+			expect( child2 ).to.be.instanceOf( Element );
+			expect( child2.name ).to.equal( 'b' );
+			expect( child2.getChildCount() ).to.equal( 1 );
+			expect( child2.getChild( 0 ).data ).to.equal( 'bar' );
+		} );
+
+		// <p><b><u>|foobar</u></b></p> -> <p>|<b><u>foobar</u></b></p>
+		it( '<p><b><u>|foobar</u></b></p>', () => {
+			const writer = new Writer();
+			const text = new Text( 'foobar' );
+			const u = new Element( 'u', {}, [ text ] );
+			const b = new Element( 'b', {}, [ u ] );
+			const p = new Element( 'p', {}, [ b ] );
+			const position = new Position( text, 0 );
+			writer.setPriority( u, 1 );
+			writer.setPriority( b, 1 );
+
+			const newPosition = writer.breakAttributes( position );
+			const parent = newPosition.parent;
+
+			expect( parent ).to.equal( p );
+			expect( newPosition.offset ).to.equal( 0 );
+			expect( p.getChildCount() ).to.equal( 1 );
+			const b1 = p.getChild( 0 );
+			expect( b1 ).to.equal( b );
+			expect( b1.getChildCount() ).to.equal( 1 );
+			const u1 = b.getChild( 0 );
+			expect( u1 ).to.equal( u );
+			expect( u1.getChildCount() ).to.equal( 1 );
+			const text1 = u1.getChild( 0 );
+			expect( text1 ).to.equal( text );
+			expect( text1.data ).to.equal( 'foobar' );
+		} );
+
+		// <p><b><u>foo|bar</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 writer = new Writer();
+			const text = new Text( 'foobar' );
+			const u = new Element( 'u', {}, [ text ] );
+			const b = new Element( 'b', {}, [ u ] );
+			const p = new Element( 'p', {}, [ b ] );
+			const position = new Position( text, 3 );
+			writer.setPriority( u, 1 );
+			writer.setPriority( b, 1 );
+
+			const newPosition = writer.breakAttributes( position );
+			const parent = newPosition.parent;
+
+			expect( parent ).to.equal( p );
+			expect( newPosition.offset ).to.equal( 1 );
+			expect( parent.getChildCount() ).to.equal( 2 );
+			const child1 = parent.getChild( 0 );
+			const child2 = parent.getChild( 1 );
+			expect( child1.name ).to.equal( 'b' );
+			expect( child1.name ).to.equal( 'b' );
+			expect( child1.getChildCount() ).to.equal( 1 );
+			expect( child2.getChildCount() ).to.equal( 1 );
+
+			const u1 = child1.getChild( 0 );
+			const u2 = child2.getChild( 0 );
+
+			expect( u1.name ).to.equal( 'u' );
+			expect( u2.name ).to.equal( 'u' );
+
+			expect( u1.getChildCount() ).to.equal( 1 );
+			expect( u2.getChildCount() ).to.equal( 1 );
+
+			expect( u1.getChild( 0 ).data ).to.equal( 'foo' );
+			expect( u2.getChild( 0 ).data ).to.equal( 'bar' );
+		} );
+
+		// <p><b><u>foobar|</u></b></p> -> <p><b><u>foobar</u></b>|</p>
+		it( '<p><b><u>|foobar</u></b></p>', () => {
+			const writer = new Writer();
+			const text = new Text( 'foobar' );
+			const u = new Element( 'u', {}, [ text ] );
+			const b = new Element( 'b', {}, [ u ] );
+			const p = new Element( 'p', {}, [ b ] );
+			const position = new Position( text, 6 );
+			writer.setPriority( u, 1 );
+			writer.setPriority( b, 1 );
+
+			const newPosition = writer.breakAttributes( position );
+			const parent = newPosition.parent;
+
+			expect( parent ).to.equal( p );
+			expect( newPosition.offset ).to.equal( 1 );
+			expect( p.getChildCount() ).to.equal( 1 );
+			const b1 = p.getChild( 0 );
+			expect( b1 ).to.equal( b );
+			expect( b1.getChildCount() ).to.equal( 1 );
+			const u1 = b.getChild( 0 );
+			expect( u1 ).to.equal( u );
+			expect( u1.getChildCount() ).to.equal( 1 );
+			const text1 = u1.getChild( 0 );
+			expect( text1 ).to.equal( text );
+			expect( text1.data ).to.equal( 'foobar' );
+		} );
+	} );
 } );