瀏覽代碼

Tests and docs for fillers.

Piotr Jasiun 9 年之前
父節點
當前提交
f632ce8c3c
共有 2 個文件被更改,包括 207 次插入2 次删除
  1. 73 2
      packages/ckeditor5-engine/src/treeview/filler.js
  2. 134 0
      packages/ckeditor5-engine/tests/treeview/filler.js

+ 73 - 2
packages/ckeditor5-engine/src/treeview/filler.js

@@ -6,13 +6,38 @@
 'use strict';
 
 /**
- * Set of utils related to keyboard support.
+ * Set of utils related to block and inline filler handling.
+ *
+ * Browsers do not allow to put caret in an element which does not have hight. Because of it, we need to fill all
+ * empty elements which should be selectable with elements or characters called "fillers". Unfortunately there is no one
+ * universal filler, this is why two types are uses:
+ *
+ * Block filler is an element which fill blocks, like `<p>`. CKEditor use `<br>` as a block filler during the editing,
+ * as browsers do natively. So instead of empty `<p>` there will be `<p><br></p>`. The advantage of block filler is that
+ * it is transparent for for selection, so when the caret is before the `<br>` and user press right arrow he will be
+ * moved to the next paragraph, not after the `<br>`. The disadvantage is that it breaks a block, so it can not be used
+ * in the middle of the text. {@link engine.treeView.filler.BR_FILLER <br> filler} can be replaced with any other
+ * character in the data output, for instance {@link engine.treeView.filler.NBSP_FILLER non breaking space}.
+ *
+ * Inline filler is a filler which does not break text, so can be used inside the text, for instance in the empty
+ * `<b>` surrendered by text: `foo<b></b>bar`, if we want to put caret there. CKEditor use a sequence of the zero width
+ * spaces as a {@link engine.treeView.filler.INLINE_FILLER inline filler} having the predetermined
+ * {@link engine.treeView.filler.INLINE_FILLER_LENGTH length}. The sequence is used, instead of the single character to
+ * avoid threating random zero width spaces as an inline filler. Disadvantage of the inline filler is that it is not
+ * transparent for the selection. The arrow key move the caret between zero with spaces characters, so the additional
+ * code is needed to handle caret.
+ *
+ * Both inline and block fillers are handled by the {@link engine.treeView.renderer renderer} and are not present in the
+ * view.
  *
  * @namespace engine.treeView.filler
  */
 
 /**
- * @member {Object} engine.treeView.filler.BR_FILLER
+ * Br filler creator. This is a function which creates `<br data-filler="true">` element, but should be understand as
+ * configuration option more then used directly.
+ *
+ * @member {Function} engine.treeView.filler.BR_FILLER
  */
 export const BR_FILLER = ( domDocument ) => {
 	const fillerBr = domDocument.createElement( 'br' );
@@ -21,20 +46,55 @@ export const BR_FILLER = ( domDocument ) => {
 	return fillerBr;
 };
 
+/**
+ * Nbsp filler creator. This is a function which creates `&nbsp;` text node, but should be understand as
+ * configuration option more then used directly.
+ *
+ * @member {Function} engine.treeView.filler.NBSP_FILLER_FILLER
+ */
 export const NBSP_FILLER = ( domDocument ) => domDocument.createTextNode( '&nbsp;' );
 
+/**
+ * Length of the {@link engine.treeView.filler.INLINE_FILLER INLINE_FILLER}.
+ *
+ * @member {Function} engine.treeView.filler.INLINE_FILLER_LENGTH
+ */
 export const INLINE_FILLER_LENGTH = 7;
 
+/**
+ * Inline filler which is sequence of the zero width spaces.
+ *
+ * @member {String} engine.treeView.filler.INLINE_FILLER
+ */
 export let INLINE_FILLER = '';
 
 for ( let i = 0; i < INLINE_FILLER_LENGTH; i++ ) {
 	INLINE_FILLER += '\u200b';
 }
 
+/**
+ * Check if text node starts with {@link engine.treeView.filler.INLINE_FILLER inline filler}.
+ *
+ *		startsWithFiller( document.createTextNode( INLINE_FILLER ) ); // true
+ *		startsWithFiller( document.createTextNode( INLINE_FILLER + 'foo' ) ); // true
+ *		startsWithFiller( document.createTextNode( 'foo' ) ); // false
+ *
+ * @param {Text} domText DOM text node.
+ * @returns {Boolean} True if text node starts with {@link engine.treeView.filler.INLINE_FILLER inline filler}.
+ */
 export function startsWithFiller( domText ) {
 	return ( domText.data.substr( 0, INLINE_FILLER_LENGTH ) === INLINE_FILLER );
 }
 
+/**
+ * Check if text node contains only {@link engine.treeView.filler.INLINE_FILLER inline filler}.
+ *
+ *		isInlineFiller( document.createTextNode( INLINE_FILLER ) ); // true
+ *		isInlineFiller( document.createTextNode( INLINE_FILLER + 'foo' ) ); // false
+ *
+ * @param {Text} domText DOM text node.
+ * @returns {Boolean} True if text node contains only {@link engine.treeView.filler.INLINE_FILLER inline filler}.
+ */
 export function isInlineFiller( domText ) {
 	return domText.data.length == INLINE_FILLER_LENGTH && startsWithFiller( domText );
 }
@@ -47,8 +107,19 @@ export function getDataWithoutFiller( domText ) {
 	}
 }
 
+// Cache block fillers templates to improve performance.
 const templateBlockFillers = new WeakMap();
 
+/**
+ * Check if the node is an instance of the block filler of the given type.
+ *
+ *		const brFillerInstance = BR_FILLER( document );
+ *		isBlockFiller( brFillerInstance, BR_FILLER ); // true
+ *
+ * @param {Node} domNode DOM node to check.
+ * @param {Function} blockFiller Block filler creator.
+ * @returns {Boolean} True if text node contains only {@link engine.treeView.filler.INLINE_FILLER inline filler}.
+ */
 export function isBlockFiller( domNode, blockFiller ) {
 	let templateBlockFiller = templateBlockFillers.get( blockFiller );
 

+ 134 - 0
packages/ckeditor5-engine/tests/treeview/filler.js

@@ -0,0 +1,134 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* bender-tags: treeview */
+
+'use strict';
+
+import {
+	BR_FILLER,
+	NBSP_FILLER,
+	INLINE_FILLER_LENGTH,
+	INLINE_FILLER,
+	startsWithFiller,
+	isInlineFiller,
+	getDataWithoutFiller,
+	isBlockFiller
+} from '/ckeditor5/engine/treeview/filler.js';
+
+describe( 'filler', () => {
+	describe( 'INLINE_FILLER', () => {
+		it( 'should have length equal INLINE_FILLER_LENGTH', () => {
+			expect( INLINE_FILLER.length ).to.equal( INLINE_FILLER_LENGTH );
+		} );
+	} );
+
+	describe( 'startsWithFiller', () => {
+		it( 'should be true for element which contains only filler', () => {
+			const node = document.createTextNode( INLINE_FILLER );
+
+			expect( startsWithFiller( node ) ).to.be.true;
+		} );
+
+		it( 'should be true for element which starts with filler', () => {
+			const node = document.createTextNode( INLINE_FILLER + 'foo' );
+
+			expect( startsWithFiller( node ) ).to.be.true;
+		} );
+
+		it( 'should be false which contains filler in the middle', () => {
+			const node = document.createTextNode( 'x' + INLINE_FILLER + 'x' );
+
+			expect( startsWithFiller( node ) ).to.be.false;
+		} );
+
+		it( 'should be false for the node which does not contains filler', () => {
+			const node = document.createTextNode( 'foo' );
+
+			expect( startsWithFiller( node ) ).to.be.false;
+		} );
+
+		it( 'should be false for the node which does not contains filler, even if it has the same length', () => {
+			let text = '';
+
+			for ( let i = 0; i < INLINE_FILLER_LENGTH; i++ ) {
+				text += 'x';
+			}
+
+			const node = document.createTextNode( text );
+
+			expect( startsWithFiller( node ) ).to.be.false;
+		} );
+	} );
+
+	describe( 'getDataWithoutFiller', () => {
+		it( 'should return data without filler', () => {
+			const node = document.createTextNode( INLINE_FILLER + 'foo' );
+
+			const dataWithoutFiller = getDataWithoutFiller( node );
+
+			expect( dataWithoutFiller.length ).to.equals( 3 );
+			expect( dataWithoutFiller ).to.equals( 'foo' );
+		} );
+
+		it( 'should return the same data for data without filler', () => {
+			const node = document.createTextNode( 'foo' );
+
+			const dataWithoutFiller = getDataWithoutFiller( node );
+
+			expect( dataWithoutFiller.length ).to.equals( 3 );
+			expect( dataWithoutFiller ).to.equals( 'foo' );
+		} );
+	} );
+
+	describe( 'isInlineFiller', () => {
+		it( 'should be true for inline filler', () => {
+			const node = document.createTextNode( INLINE_FILLER );
+
+			expect( isInlineFiller( node ) ).to.be.true;
+		} );
+
+		it( 'should be false for element which starts with filler', () => {
+			const node = document.createTextNode( INLINE_FILLER + 'foo' );
+
+			expect( isInlineFiller( node ) ).to.be.false;
+		} );
+
+		it( 'should be false for the node which does not contains filler, even if it has the same length', () => {
+			let text = '';
+
+			for ( let i = 0; i < INLINE_FILLER_LENGTH; i++ ) {
+				text += 'x';
+			}
+
+			const node = document.createTextNode( text );
+
+			expect( isInlineFiller( node ) ).to.be.false;
+		} );
+	} );
+
+	describe( 'isBlockFiller', () => {
+		it( 'should return true if the node is an instance of the BR block filler', () => {
+			const brFillerInstance = BR_FILLER( document );
+
+			expect( isBlockFiller( brFillerInstance, BR_FILLER ) ).to.be.true;
+			// Check it twice to ensure that caching breaks nothing.
+			expect( isBlockFiller( brFillerInstance, BR_FILLER ) ).to.be.true;
+		} );
+
+		it( 'should return true if the node is an instance of the NBSP block filler', () => {
+			const nbspFillerInstance = NBSP_FILLER( document );
+
+			expect( isBlockFiller( nbspFillerInstance, NBSP_FILLER ) ).to.be.true;
+			// Check it twice to ensure that caching breaks nothing.
+			expect( isBlockFiller( nbspFillerInstance, NBSP_FILLER ) ).to.be.true;
+		} );
+
+		it( 'should return false for inline filler', () => {
+			expect( isBlockFiller( document.createTextNode( INLINE_FILLER ), BR_FILLER ) ).to.be.false;
+			expect( isBlockFiller( document.createTextNode( INLINE_FILLER ), NBSP_FILLER ) ).to.be.false;
+		} );
+	} );
+} );