8
0
Piotr Jasiun 10 лет назад
Родитель
Сommit
3fcdfe1c4d

+ 44 - 42
packages/ckeditor5-engine/src/treeview/converter.js

@@ -12,23 +12,50 @@ const _domToViewMapping = new WeakMap();
 const _viewToDomMapping = new WeakMap();
 
 const converter = {
-	viewToDom: function( view, domDocument ) {
-		if ( view instanceof ViewText ) {
-			return domDocument.createTextNode( view.getText() );
+	bindElements: function( domElement, viewElement ) {
+		_domToViewMapping.set( domElement, viewElement );
+		_viewToDomMapping.set( viewElement, domElement );
+	},
+
+	compareNodes: function( domNode, viewNode ) {
+		// Elements.
+		if ( domNode instanceof HTMLElement && viewNode instanceof ViewElement ) {
+			return domNode === converter.getCorespondingDomElement( viewNode );
+		}
+		// Texts.
+		else if ( domNode instanceof Text && viewNode instanceof ViewText ) {
+			return domNode.data === viewNode.getText();
+		}
+
+		// Not matching types.
+		return false;
+	},
+
+	cloneDomAttrs: function( domElement, viewElement ) {
+		for ( let i = domElement.attributes.length - 1; i >= 0; i-- ) {
+			let attr = domElement.attributes[ i ];
+			viewElement.setAttr( attr.name, attr.value );
+		}
+	},
+
+	// viewToDom do bind elements
+	viewToDom: function( viewNode, domDocument ) {
+		if ( viewNode instanceof ViewText ) {
+			return domDocument.createTextNode( viewNode.getText() );
 		} else {
-			if ( converter.getCorespondingDom( view ) ) {
-				return converter.getCorespondingDom( view );
+			if ( converter.getCorespondingDom( viewNode ) ) {
+				return converter.getCorespondingDom( viewNode );
 			}
 
-			const domElement = domDocument.createElement( view.name );
-			converter.bindElement( view, domElement );
+			const domElement = domDocument.createElement( viewNode.name );
+			converter.bindElements( domElement, viewNode );
 
-			for ( let key of view.getAttrKeys() ) {
-				domElement.setAttribute( key, view.getAttr( key ) );
+			for ( let key of viewNode.getAttrKeys() ) {
+				domElement.setAttribute( key, viewNode.getAttr( key ) );
 			}
 
-			for ( let childView of view.getChildren() ) {
-				domElement.appendChild( converter.viewToDom( childView ) );
+			for ( let childView of viewNode.getChildren() ) {
+				domElement.appendChild( converter.viewToDom( childView, domDocument ) );
 			}
 
 			return domElement;
@@ -36,6 +63,7 @@ const converter = {
 	},
 
 	// Note that created elements will not have coresponding DOM elements created it these did not exist before.
+	// domToView do not bind elements
 	domToView: function( domElement ) {
 		let viewElement = converter.getCorespondingView( domElement );
 
@@ -46,7 +74,7 @@ const converter = {
 		if ( domElement instanceof Text ) {
 			return new ViewText( domElement.data );
 		} else {
-			viewElement = new Element( domElement.tagName.toLowerCase() );
+			viewElement = new ViewElement( domElement.tagName.toLowerCase() );
 			const attrs = domElement.attributes;
 
 			for ( let i = attrs.length - 1; i >= 0; i-- ) {
@@ -63,25 +91,11 @@ const converter = {
 		}
 	},
 
-	compareNodes: function( domNode, viewNode ) {
-		// Elements.
-		if ( domNode instanceof HTMLElement && viewNode instanceof ViewElement ) {
-			return domNode === converter.getCorespondingDomElement( viewNode );
-		}
-		// Texts.
-		else if ( domNode instanceof Text && viewNode instanceof ViewText ) {
-			return domNode.data === viewNode.getText();
-		}
-
-		// Not matching types.
-		return false;
-	},
-
 	getCorespondingView: function( domNode ) {
 		if ( domNode instanceof HTMLElement ) {
-			return domNode.getCorespondingViewElement( domNode );
+			return converter.getCorespondingViewElement( domNode );
 		} else {
-			return domNode.getCorespondingViewText( domNode );
+			return converter.getCorespondingViewText( domNode );
 		}
 	},
 
@@ -94,13 +108,13 @@ const converter = {
 		const previousSibling = domText.previousSibling;
 
 		if ( previousSibling ) {
-			const viewElement = Element.getCorespondingView( previousSibling );
+			const viewElement = converter.getCorespondingView( previousSibling );
 
 			if ( viewElement ) {
 				return viewElement.getNextSibling();
 			}
 		} else {
-			const viewElement = Element.getCorespondingView( domText.parentElement );
+			const viewElement = converter.getCorespondingView( domText.parentElement );
 
 			if ( viewElement ) {
 				return viewElement.getChild( 0 );
@@ -135,18 +149,6 @@ const converter = {
 
 		return null;
 	},
-
-	bindElement: function( viewElement, domElement ) {
-		_domToViewMapping.set( domElement, viewElement );
-		_viewToDomMapping.set( viewElement, domElement );
-	},
-
-	cloneDomAttrs: function( domElement, viewElement ) {
-		for ( let i = domElement.attributes.length - 1; i >= 0; i-- ) {
-			let attr = domElement.attributes[ i ];
-			viewElement.setAttr( attr.name, attr.value );
-		}
-	}
 };
 
 export default converter;

+ 4 - 5
packages/ckeditor5-engine/src/treeview/observer/mutationobserver.js

@@ -6,8 +6,7 @@
 'use strict';
 
 import Observer from './observer.js';
-import ViewElement from '../element.js';
-import ViewText from '../text.js';
+import converter from '../converter.js';
 import objectUtils from '../../lib/lodash/object.js';
 import EmitterMixin from '../../emittermixin.js';
 
@@ -55,7 +54,7 @@ export default class MutationObserver extends Observer {
 
 		for ( let mutation of domMutations ) {
 			if ( mutation.type === 'childList' ) {
-				const element = ViewElement.getCorespondingView( mutation.target );
+				const element = converter.getCorespondingView( mutation.target );
 
 				if ( element ) {
 					mutatedElements.add( element );
@@ -65,7 +64,7 @@ export default class MutationObserver extends Observer {
 
 		for ( let mutation of domMutations ) {
 			if ( mutation.type === 'characterData' ) {
-				const text = ViewText.getCorespondingView( mutation.target );
+				const text = converter.getCorespondingView( mutation.target );
 
 				if ( text && !mutatedElements.has( text.parent ) ) {
 					mutatedTexts.set( text, {
@@ -93,7 +92,7 @@ export default class MutationObserver extends Observer {
 			const newViewChildren = [];
 
 			for ( let i = 0; i < domChildren.length; i++ ) {
-				newViewChildren.push( ViewElement.createFromDom( domChildren[ i ] ) );
+				newViewChildren.push( converter.createFromDom( domChildren[ i ] ) );
 			}
 
 			viewElement.markToSync( 'CHILDREN_NEED_UPDATE' );

+ 3 - 3
packages/ckeditor5-engine/src/treeview/renderer.js

@@ -97,13 +97,13 @@ export default class Renderer {
 			let i = 0;
 
 			for ( let action of actions ) {
-				if ( action === 'EQUAL' ) {
-					i++;
-				} else if ( action === 'INSERT' ) {
+				if ( action === 'INSERT' ) {
 					domElement.insertBefore( converter.viewToDom( viewChildren[ i ], domDocument ), domChildren[ i ] || null  );
 					i++;
 				} else if ( action === 'DELETE' ) {
 					domElement.removeChild( domChildren[ i ] );
+				} else { // 'EQUAL'
+					i++;
 				}
 			}
 		}

+ 1 - 1
packages/ckeditor5-engine/src/treeview/treeview.js

@@ -26,7 +26,7 @@ export default class TreeView {
 		 */
 		this.viewRoot = new RootElement( domRoot.tagName.toLowerCase(), this );
 		converter.cloneDomAttrs( domRoot, this.viewRoot );
-		converter.bindElement( this.viewRoot, domRoot );
+		converter.bindElements( domRoot, this.viewRoot );
 
 		this.renderer = new Renderer( this );
 		this.renderer.markToSync( this.viewRoot, 'CHILDREN_NEED_UPDATE' );

+ 339 - 0
packages/ckeditor5-engine/tests/treeview/converter.js

@@ -0,0 +1,339 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* bender-tags: treeview */
+
+/* bender-include: ../_tools/tools.js */
+
+'use strict';
+
+const getIteratorCount = bender.tools.core.getIteratorCount;
+
+const modules = bender.amd.require(
+	'core/treeview/node',
+	'core/treeview/element',
+	'core/treeview/text',
+	'core/treeview/converter'
+);
+
+describe( 'converter', () => {
+	let converter, ViewText, ViewElement, ViewNode;
+
+	before( () => {
+		ViewText = modules[ 'core/treeview/text' ];
+		ViewElement = modules[ 'core/treeview/element' ];
+		ViewNode = modules[ 'core/treeview/node' ];
+		converter = modules[ 'core/treeview/converter' ];
+	} );
+
+	describe( 'bindElements', () => {
+		it( 'should bind elements', () => {
+			const domElement = document.createElement( 'p' );
+			const viewElement = new ViewElement( 'p' );
+
+			converter.bindElements( domElement, viewElement );
+
+			expect( converter.getCorespondingView( domElement ) ).to.equal( viewElement );
+			expect( converter.getCorespondingDom( viewElement ) ).to.equal( domElement );
+		} );
+	} );
+
+	describe( 'compareNodes', () => {
+		it( 'should return false for nodes not matching types', () => {
+			const domElement = document.createElement( 'p' );
+			const viewText = new ViewText( 'foo' );
+
+			expect( converter.compareNodes( domElement, viewText ) ).to.be.false;
+		} );
+
+		it( 'should return true for binded elements', () => {
+			const domElement = document.createElement( 'p' );
+			const viewElement = new ViewElement( 'p' );
+
+			converter.bindElements( domElement, viewElement );
+
+			expect( converter.compareNodes( domElement, viewElement ) ).to.be.true;
+		} );
+
+		it( 'should return true for the same texts', () => {
+			const domText = document.createTextNode( 'foo' );
+			const viewText = new ViewText( 'foo' );
+
+			expect( converter.compareNodes( domText, viewText ) ).to.be.true;
+		} );
+	} );
+
+	describe( 'cloneDomAttrs', () => {
+		it( 'should clone DOM attributes', () => {
+			const domElement = document.createElement( 'p' );
+			domElement.setAttribute( 'foo', '1' );
+			domElement.setAttribute( 'bar', '2' );
+
+			const viewElement = new ViewElement( 'p' );
+
+			converter.cloneDomAttrs( domElement, viewElement );
+
+			expect( viewElement.getAttr( 'foo' ) ).to.equal( '1' );
+			expect( viewElement.getAttr( 'bar' ) ).to.equal( '2' );
+			expect( getIteratorCount( viewElement.getAttrKeys() ) ).to.equal( 2 );
+		} );
+	} );
+
+	describe( 'domToView', () => {
+		it( 'should create tree of view elements from DOM elements', () => {
+			const domImg = document.createElement( 'img' );
+			const domText = document.createTextNode( 'foo' );
+			const domP = document.createElement( 'p' );
+
+			domP.setAttribute( 'class', 'foo' );
+
+			domP.appendChild( domImg );
+			domP.appendChild( domText );
+
+			const viewImg = new ViewElement( 'img' );
+
+			converter.bindElements( domImg, viewImg );
+
+			const viewP = converter.domToView( domP );
+
+			expect( viewP ).to.be.an.instanceof( ViewElement );
+			expect( viewP.name ).to.equal( 'p' );
+
+			expect( viewP.getAttr( 'class' ) ).to.equal( 'foo' );
+			expect( getIteratorCount( viewP.getAttrKeys() ) ).to.equal( 1 );
+
+			expect( viewP.getChildCount() ).to.equal( 2 );
+			expect( viewP.getChild( 0 ).name ).to.equal( 'img' );
+			expect( viewP.getChild( 1 ).getText() ).to.equal( 'foo' );
+
+			expect( converter.getCorespondingDom( viewP ) ).to.not.equal( domP );
+			expect( converter.getCorespondingDom( viewP.getChild( 0 ) ) ).to.equal( domImg );
+		} );
+	} );
+
+	describe( 'viewToDom', () => {
+		it( 'should create tree of DOM elements from view elements', () => {
+			const viewImg = new ViewElement( 'img' );
+			const viewText = new ViewText( 'foo' );
+			const viewP = new ViewElement( 'p' );
+
+			viewP.setAttr( 'class', 'foo' );
+
+			viewP.appendChildren( viewImg );
+			viewP.appendChildren( viewText );
+
+			const domImg = document.createElement( 'img' );
+
+			converter.bindElements( domImg, viewImg );
+
+			const domP = converter.viewToDom( viewP, document );
+
+			expect( domP ).to.be.an.instanceof( HTMLElement );
+			expect( domP.tagName.toLowerCase() ).to.equal( 'p' );
+
+			expect( domP.getAttribute( 'class' ) ).to.equal( 'foo' );
+			expect( domP.attributes.length ).to.equal( 1 );
+
+			expect( domP.childNodes.length ).to.equal( 2 );
+			expect( domP.childNodes[ 0 ].tagName.toLowerCase() ).to.equal( 'img' );
+			expect( domP.childNodes[ 1 ].data ).to.equal( 'foo' );
+
+			expect( converter.getCorespondingView( domP ) ).to.equal( viewP );
+			expect( converter.getCorespondingView( domP.childNodes[ 0 ] ) ).to.equal( viewImg );
+		} );
+	} );
+
+	describe( 'getCorespondingView', () => {
+		it( 'should return coresponding view element if element is passed', () => {
+			const domElement = document.createElement( 'p' );
+			const viewElement = new ViewElement( 'p' );
+
+			converter.bindElements( domElement, viewElement );
+
+			expect( converter.getCorespondingView( domElement ) ).to.equal( viewElement );
+		} );
+
+		it( 'should return coresponding view text if text is passed', () => {
+			const domText = document.createTextNode( 'foo' );
+			const domP = document.createElement( 'p' );
+
+			domP.appendChild( domText );
+
+			const viewP = converter.domToView( domP );
+			const viewText = viewP.getChild( 0 );
+
+			converter.bindElements( domP, viewP );
+
+			expect( converter.getCorespondingView( domText ) ).to.equal( viewText );
+		} );
+	} );
+
+	describe( 'getCorespondingViewElement', () => {
+		it( 'should return coresponding view element', () => {
+			const domElement = document.createElement( 'p' );
+			const viewElement = new ViewElement( 'p' );
+
+			converter.bindElements( domElement, viewElement );
+
+			expect( converter.getCorespondingViewElement( domElement ) ).to.equal( viewElement );
+		} );
+	} );
+
+	describe( 'getCorespondingViewText', () => {
+		it( 'should return coresponding view text based on sibling', () => {
+			const domImg = document.createElement( 'img' );
+			const domText = document.createTextNode( 'foo' );
+			const domP = document.createElement( 'p' );
+
+			domP.appendChild( domImg );
+			domP.appendChild( domText );
+
+			const viewImg = new ViewElement( 'img' );
+
+			converter.bindElements( domImg, viewImg );
+
+			const viewP = converter.domToView( domP );
+			const viewText = viewP.getChild( 1 );
+
+			expect( converter.getCorespondingViewText( domText ) ).to.equal( viewText );
+		} );
+
+		it( 'should return coresponding view text based on parent', () => {
+			const domText = document.createTextNode( 'foo' );
+			const domP = document.createElement( 'p' );
+
+			domP.appendChild( domText );
+
+			const viewP = converter.domToView( domP );
+			const viewText = viewP.getChild( 0 );
+
+			converter.bindElements( domP, viewP );
+
+			expect( converter.getCorespondingViewText( domText ) ).to.equal( viewText );
+		} );
+
+		it( 'should return null if sibling is not binded', () => {
+			const domImg = document.createElement( 'img' );
+			const domText = document.createTextNode( 'foo' );
+			const domP = document.createElement( 'p' );
+
+			domP.appendChild( domImg );
+			domP.appendChild( domText );
+
+			const viewP = converter.domToView( domP );
+
+			converter.bindElements( domP, viewP );
+
+			expect( converter.getCorespondingViewText( domText ) ).to.be.null;
+		} );
+
+		it( 'should return null if parent is not binded', () => {
+			const domText = document.createTextNode( 'foo' );
+			const domP = document.createElement( 'p' );
+
+			domP.appendChild( domText );
+
+			expect( converter.getCorespondingViewText( domText ) ).to.be.null;
+		} );
+	} );
+
+	describe( 'getCorespondingDom', () => {
+		it( 'should return coresponding DOM element if element was passed', () => {
+			const domElement = document.createElement( 'p' );
+			const viewElement = new ViewElement( 'p' );
+
+			converter.bindElements( domElement, viewElement );
+
+			expect( converter.getCorespondingDom( viewElement ) ).to.equal( domElement );
+		} );
+
+		it( 'should return coresponding DOM text if text was passed', () => {
+			const domText = document.createTextNode( 'foo' );
+			const domP = document.createElement( 'p' );
+
+			domP.appendChild( domText );
+
+			const viewP = converter.domToView( domP );
+			const viewText = viewP.getChild( 0 );
+
+			converter.bindElements( domP, viewP );
+
+			expect( converter.getCorespondingDom( viewText ) ).to.equal( domText );
+		} );
+	} );
+
+	describe( 'getCorespondingDomElement', () => {
+		it( 'should return coresponding DOM element', () => {
+			const domElement = document.createElement( 'p' );
+			const viewElement = new ViewElement( 'p' );
+
+			converter.bindElements( domElement, viewElement );
+
+			expect( converter.getCorespondingDomElement( viewElement ) ).to.equal( domElement );
+		} );
+	} );
+
+	describe( 'getCorespondingDomText', () => {
+		it( 'should return coresponding DOM text based on sibling', () => {
+			const domImg = document.createElement( 'img' );
+			const domText = document.createTextNode( 'foo' );
+			const domP = document.createElement( 'p' );
+
+			domP.appendChild( domImg );
+			domP.appendChild( domText );
+
+			const viewImg = new ViewElement( 'img' );
+
+			converter.bindElements( domImg, viewImg );
+
+			const viewP = converter.domToView( domP );
+			const viewText = viewP.getChild( 1 );
+
+			expect( converter.getCorespondingDomText( viewText ) ).to.equal( domText );
+		} );
+
+		it( 'should return coresponding DOM text based on parent', () => {
+			const domText = document.createTextNode( 'foo' );
+			const domP = document.createElement( 'p' );
+
+			domP.appendChild( domText );
+
+			const viewP = converter.domToView( domP );
+			const viewText = viewP.getChild( 0 );
+
+			converter.bindElements( domP, viewP );
+
+			expect( converter.getCorespondingDomText( viewText ) ).to.equal( domText );
+		} );
+
+		it( 'should return null if sibling is not binded', () => {
+			const domImg = document.createElement( 'img' );
+			const domText = document.createTextNode( 'foo' );
+			const domP = document.createElement( 'p' );
+
+			domP.appendChild( domImg );
+			domP.appendChild( domText );
+
+			const viewP = converter.domToView( domP );
+			const viewText = viewP.getChild( 1 );
+
+			converter.bindElements( domP, viewP );
+
+			expect( converter.getCorespondingDomText( viewText ) ).to.be.null;
+		} );
+
+		it( 'should return null if parent is not binded', () => {
+			const domText = document.createTextNode( 'foo' );
+			const domP = document.createElement( 'p' );
+
+			domP.appendChild( domText );
+
+			const viewP = converter.domToView( domP );
+			const viewText = viewP.getChild( 0 );
+
+			expect( converter.getCorespondingDomText( viewText ) ).to.be.null;
+		} );
+	} );
+} );

+ 22 - 3
packages/ckeditor5-engine/tests/treeview/renderer.js

@@ -36,7 +36,7 @@ describe( 'Renderer', () => {
 			viewNode = new ViewElement( 'p' );
 
 			const domNode = document.createElement( 'p' );
-			converter.bindElement( viewNode, domNode );
+			converter.bindElements( domNode, viewNode );
 			viewNode.appendChildren( new ViewText( 'foo' ) );
 
 			renderer.markedTexts.clear();
@@ -108,7 +108,7 @@ describe( 'Renderer', () => {
 			viewNode = new ViewElement( 'p' );
 			domNode = document.createElement( 'p' );
 
-			converter.bindElement( viewNode, domNode );
+			converter.bindElements( domNode, viewNode );
 
 			renderer.markedTexts.clear();
 			renderer.markedAttrs.clear();
@@ -152,6 +152,25 @@ describe( 'Renderer', () => {
 			expect( renderer.markedChildren.size ).to.equal( 0 );
 		} );
 
+		it( 'should remove children', () => {
+			viewNode.appendChildren( new ViewText( 'foo' ) );
+
+			renderer.markToSync( viewNode, 'CHILDREN_NEED_UPDATE' );
+			renderer.render();
+
+			expect( domNode.childNodes.length ).to.equal( 1 );
+			expect( domNode.childNodes[ 0 ].data ).to.equal( 'foo' );
+
+			viewNode.removeChildren( 0, 1 );
+
+			renderer.markToSync( viewNode, 'CHILDREN_NEED_UPDATE' );
+			renderer.render();
+
+			expect( domNode.childNodes.length ).to.equal( 0 );
+
+			expect( renderer.markedChildren.size ).to.equal( 0 );
+		} );
+
 		it( 'should update text', () => {
 			const viewText = new ViewText( 'foo' );
 			viewNode.appendChildren( viewText );
@@ -229,7 +248,7 @@ describe( 'Renderer', () => {
 			const domImg = document.createElement( 'img' );
 			domNode.appendChild( domImg );
 
-			converter.bindElement( viewImg, domImg );
+			converter.bindElements( domImg, viewImg );
 
 			renderer.markToSync( viewNode, 'CHILDREN_NEED_UPDATE' );
 			renderer.render();