8
0
Просмотр исходного кода

TreeView.getRoot() method introduced. Tests for TreeView. Especially for _jumpOverInlineFiller.

Piotr Jasiun 9 лет назад
Родитель
Сommit
23d9033342

+ 13 - 2
packages/ckeditor5-engine/src/treeview/treeview.js

@@ -140,10 +140,10 @@ export default class TreeView {
 	 * root element will be removed but the root name and attributes will be preserved.
 	 *
 	 * @param {HTMLElement} domRoot DOM element in which the tree view should do change.
-	 * @param {String} name Name of the root.
+	 * @param {String} [name='main'] Name of the root.
 	 * @returns {engine.treeView.element} The created view root element.
 	 */
-	createRoot( domRoot, name ) {
+	createRoot( domRoot, name = 'main' ) {
 		const viewRoot = this.domConverter.domToView( domRoot, { bind: true, withChildren: false } );
 		viewRoot.setTreeView( this );
 
@@ -164,6 +164,17 @@ export default class TreeView {
 	}
 
 	/**
+	 * Get a {@link engine.treeView.TreeView#viewRoots view root element} with the specified name. If the name is not
+	 * specific "main" root is returned.
+	 *
+	 * @param {String} [name='main']  Name of the root.
+	 * @returns {engine.treeView.element} The view root element with the specified name.
+	 */
+	getRoot( name = 'main' ) {
+		return this.viewRoots.get( name );
+	}
+
+	/**
 	 * Renders all changes. In order to avoid triggering the observers (e.g. mutations) all observers all detached
 	 * before rendering and reattached after that.
 	 */

+ 27 - 2
packages/ckeditor5-engine/tests/_utils/view.js

@@ -123,7 +123,7 @@ export function stringify( node, selectionOrPositionOrRange = null, options = {}
 		selection = new Selection();
 		selection.addRange( new Range( selectionOrPositionOrRange, selectionOrPositionOrRange ) );
 	} else if ( selectionOrPositionOrRange instanceof Range ) {
-		selection = new Selection( );
+		selection = new Selection();
 		selection.addRange( selectionOrPositionOrRange );
 	} else {
 		selection = selectionOrPositionOrRange;
@@ -182,7 +182,7 @@ export function stringify( node, selectionOrPositionOrRange = null, options = {}
  * @returns {engine.treeView.Text|engine.treeView.Element|engine.treeView.DocumentFragment|Object} Returns parsed view node
  * or object with two fields `view` and `selection` when selection ranges were included in data to parse.
  */
-export function parse( data, options = { } ) {
+export function parse( data, options = {} ) {
 	options.order = options.order || [];
 	const viewParser = new ViewParser();
 	const rangeParser = new RangeParser();
@@ -844,3 +844,28 @@ class ViewStringify {
 		return attributes.join( ' ' );
 	}
 }
+
+export function setData( treeView, data ) {
+	let view, selection;
+
+	const result = parse( data );
+
+	if ( result.view && result.selection ) {
+		selection = result.selection;
+		view = result.view;
+	} else {
+		view = result;
+	}
+
+	const root = treeView.getRoot();
+	root.removeChildren( 0, root.getChildCount() );
+	root.appendChildren( view );
+
+	if ( selection ) {
+		treeView.selection.setTo( selection );
+	}
+}
+
+export function getData( treeView ) {
+	return stringify( treeView.getRoot(), treeView.selection );
+}

packages/ckeditor5-engine/tests/treeview/integration.js → packages/ckeditor5-engine/tests/treeview/treeview/integration.js


+ 1 - 0
packages/ckeditor5-engine/tests/treeview/treeview/jumpoverinlinefiller.html

@@ -0,0 +1 @@
+<div contenteditable="true" id="editor"></div>

+ 101 - 0
packages/ckeditor5-engine/tests/treeview/treeview/jumpoverinlinefiller.js

@@ -0,0 +1,101 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* bender-tags: treeview */
+
+'use strict';
+
+import ViewRange from '/ckeditor5/engine/treeview/range.js';
+import TreeView from '/ckeditor5/engine/treeview/treeview.js';
+import KeyObserver from '/ckeditor5/engine/treeview/observer/keyobserver.js';
+import { INLINE_FILLER_SIZE } from '/ckeditor5/engine/treeview/domconverter.js';
+
+import { keyNames } from '/ckeditor5/utils/keyboard.js';
+
+import { parse, setData } from '/tests/engine/_utils/view.js';
+
+describe( 'TreeView', () => {
+	let treeView;
+
+	beforeEach( () => {
+		treeView = new TreeView();
+
+		treeView.addObserver( KeyObserver );
+		treeView.createRoot( document.getElementById( 'editor' ) );
+
+		document.getSelection().removeAllRanges();
+	} );
+
+	describe( 'jump over inline filler hack', () => {
+		it( 'should jump over inline filler when left arrow is pressed after inline filler', () => {
+			setData( treeView, '<container:p>foo<attribute:b>[]</attribute:b>bar</container:p>' );
+			treeView.render();
+
+			treeView.fire( 'keydown', { keyCode: keyNames.arrowleft, domTarget: treeView.domRoots.get( 'main' ) } );
+
+			const domRange = document.getSelection().getRangeAt( 0 );
+			expect( treeView.domConverter.isInlineFiller( domRange.startContainer ) ).to.be.true;
+			expect( domRange.startOffset ).to.equal( 0 );
+			expect( domRange.collapsed ).to.be.true;
+		} );
+
+		it( 'should do nothing when another key is pressed', () => {
+			setData( treeView, '<container:p>foo<attribute:b>[]</attribute:b>bar</container:p>' );
+			treeView.render();
+
+			treeView.fire( 'keydown', { keyCode: keyNames.arrowright, domTarget: treeView.domRoots.get( 'main' ) } );
+
+			const domRange = document.getSelection().getRangeAt( 0 );
+			expect( treeView.domConverter.isInlineFiller( domRange.startContainer ) ).to.be.true;
+			expect( domRange.startOffset ).to.equal( INLINE_FILLER_SIZE );
+			expect( domRange.collapsed ).to.be.true;
+		} );
+
+		it( 'should do nothing if range is not collapsed', () => {
+			setData( treeView, '<container:p>foo<attribute:b>{x}</attribute:b>bar</container:p>' );
+			treeView.render();
+
+			treeView.fire( 'keydown', { keyCode: keyNames.arrowleft, domTarget: treeView.domRoots.get( 'main' ) } );
+
+			const domRange = document.getSelection().getRangeAt( 0 );
+			expect( domRange.startContainer.data ).to.equal( 'x' );
+			expect( domRange.startOffset ).to.equal( 0 );
+			expect( domRange.endContainer.data ).to.equal( 'x' );
+			expect( domRange.endOffset ).to.equal( 1 );
+		} );
+
+		it( 'should do nothing if node does not start with filler', () => {
+			setData( treeView, '<container:p>foo<attribute:b>{}x</attribute:b>bar</container:p>' );
+			treeView.render();
+
+			treeView.fire( 'keydown', { keyCode: keyNames.arrowleft, domTarget: treeView.domRoots.get( 'main' ) } );
+
+			const domRange = document.getSelection().getRangeAt( 0 );
+			expect( domRange.startContainer.data ).to.equal( 'x' );
+			expect( domRange.startOffset ).to.equal( 0 );
+			expect( domRange.collapsed ).to.be.true;
+		} );
+
+		it( 'should do nothing if caret is not directly before filler', () => {
+			setData( treeView, '<container:p>foo<attribute:b>[]</attribute:b>bar</container:p>' );
+			treeView.render();
+
+			// '<container:p>foo<attribute:b>x{}</attribute:b>bar</container:p>'
+			const viewB = treeView.selection.getFirstPosition().parent;
+			const viewTextX = parse( 'x' );
+			viewB.appendChildren( viewTextX );
+			treeView.selection.removeAllRanges();
+			treeView.selection.addRange( ViewRange.createFromParentsAndOffsets( viewTextX, 1, viewTextX, 1 ) );
+			treeView.render();
+
+			treeView.fire( 'keydown', { keyCode: keyNames.arrowleft, domTarget: treeView.domRoots.get( 'main' ) } );
+
+			const domRange = document.getSelection().getRangeAt( 0 );
+			expect( treeView.domConverter.startsWithFiller( domRange.startContainer ) ).to.be.true;
+			expect( domRange.startOffset ).to.equal( INLINE_FILLER_SIZE + 1 );
+			expect( domRange.collapsed ).to.be.true;
+		} );
+	} );
+} );

+ 103 - 26
packages/ckeditor5-engine/tests/treeview/treeview.js

@@ -16,6 +16,36 @@ import DomConverter from '/ckeditor5/engine/treeview/domconverter.js';
 import count from '/ckeditor5/utils/count.js';
 
 describe( 'TreeView', () => {
+	let ObserverMock, ObserverMockGlobalCount, instantiated, enabled;
+
+	beforeEach( () => {
+		instantiated = 0;
+		enabled = 0;
+
+		ObserverMock = class extends Observer {
+			constructor( treeView ) {
+				super( treeView );
+
+				this.enable = sinon.spy();
+				this.disable = sinon.spy();
+				this.observe = sinon.spy();
+			}
+		};
+
+		ObserverMockGlobalCount = class extends Observer {
+			constructor( treeView ) {
+				super( treeView );
+				instantiated++;
+
+				this.observe = sinon.spy();
+			}
+
+			enable() {
+				enabled++;
+			}
+		};
+	} );
+
 	describe( 'constructor', () => {
 		it( 'should create TreeView with all properties', () => {
 			const treeView = new TreeView();
@@ -53,21 +83,71 @@ describe( 'TreeView', () => {
 			expect( viewRoot.getAttribute( 'id' ) ).to.equal( 'editor' );
 			expect( treeView.renderer.markedChildren.has( viewRoot ) ).to.be.true;
 		} );
+
+		it( 'should call observe on each observer', () => {
+			const treeView = new TreeView( document.createElement( 'div' ) );
+			treeView.renderer.render = sinon.spy();
+
+			const domDiv1 = document.createElement( 'div' );
+			domDiv1.setAttribute( 'id', 'editor' );
+
+			const domDiv2 = document.createElement( 'div' );
+			domDiv2.setAttribute( 'id', 'editor' );
+
+			treeView.addObserver( ObserverMock );
+			treeView.addObserver( ObserverMockGlobalCount );
+
+			treeView.createRoot( document.createElement( 'div' ), 'root1' );
+
+			const observerMock = treeView.getObserver( ObserverMock );
+			const observerMockGlobalCount = treeView.getObserver( ObserverMockGlobalCount );
+
+			sinon.assert.calledOnce( observerMock.observe );
+			sinon.assert.calledOnce( observerMockGlobalCount.observe );
+		} );
+
+		it( 'should create "main" root by default', () => {
+			const domDiv = document.createElement( 'div' );
+
+			const treeView = new TreeView();
+			const ret = treeView.createRoot( domDiv );
+
+			expect( count( treeView.domRoots ) ).to.equal( 1 );
+			expect( count( treeView.viewRoots ) ).to.equal( 1 );
+
+			const domRoot = treeView.domRoots.get( 'main' );
+			const viewRoot = treeView.viewRoots.get( 'main' );
+
+			expect( ret ).to.equal( viewRoot );
+
+			expect( domRoot ).to.equal( domDiv );
+		} );
 	} );
 
-	describe( 'addObserver', () => {
-		let ObserverMock, treeView;
+	describe( 'getRoot', () => {
+		it( 'should return "main" root', () => {
+			const treeView = new TreeView();
+			treeView.createRoot( document.createElement( 'div' ) );
 
-		beforeEach( () => {
-			ObserverMock = class extends Observer {
-				constructor( treeView ) {
-					super( treeView );
+			expect( count( treeView.viewRoots ) ).to.equal( 1 );
+
+			expect( treeView.getRoot() ).to.equal( treeView.viewRoots.get( 'main' ) );
+		} );
 
-					this.enable = sinon.spy();
-					this.disable = sinon.spy();
-				}
-			};
+		it( 'should return named root', () => {
+			const treeView = new TreeView();
+			treeView.createRoot( document.createElement( 'h1' ), 'header' );
 
+			expect( count( treeView.viewRoots ) ).to.equal( 1 );
+
+			expect( treeView.getRoot( 'header' ) ).to.equal( treeView.viewRoots.get( 'header' ) );
+		} );
+	} );
+
+	describe( 'addObserver', () => {
+		let treeView;
+
+		beforeEach( () => {
 			treeView = new TreeView( document.createElement( 'div' ) );
 			treeView.renderer.render = sinon.spy();
 		} );
@@ -84,22 +164,8 @@ describe( 'TreeView', () => {
 		} );
 
 		it( 'should instantiate one observer only once', () => {
-			let instantiated = 0;
-			let enabled = 0;
-
-			class ObserverMock2 extends Observer {
-				constructor( treeView ) {
-					super( treeView );
-					instantiated++;
-				}
-
-				enable() {
-					enabled++;
-				}
-			}
-
-			treeView.addObserver( ObserverMock2 );
-			treeView.addObserver( ObserverMock2 );
+			treeView.addObserver( ObserverMockGlobalCount );
+			treeView.addObserver( ObserverMockGlobalCount );
 
 			expect( treeView._observers.size ).to.equal( 1 );
 			expect( instantiated ).to.equal( 1 );
@@ -133,5 +199,16 @@ describe( 'TreeView', () => {
 			sinon.assert.calledOnce( treeView.renderer.render );
 			sinon.assert.calledTwice( observerMock.enable );
 		} );
+
+		it( 'should call observe on each root', () => {
+			treeView.createRoot( document.createElement( 'div' ), 'root1' );
+			treeView.createRoot( document.createElement( 'div' ), 'root2' );
+
+			treeView.addObserver( ObserverMock );
+
+			const observerMock = treeView.getObserver( ObserverMock );
+
+			sinon.assert.calledTwice( observerMock.observe );
+		} );
 	} );
 } );