瀏覽代碼

Added getData method to view test utils.

Szymon Kupś 9 年之前
父節點
當前提交
f7d6dbb3dc
共有 2 個文件被更改,包括 254 次插入0 次删除
  1. 94 0
      packages/ckeditor5-engine/tests/_utils-tests/view.js
  2. 160 0
      packages/ckeditor5-engine/tests/_utils/view.js

+ 94 - 0
packages/ckeditor5-engine/tests/_utils-tests/view.js

@@ -0,0 +1,94 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+import { getData } from '/tests/engine/_utils/view.js';
+import Element from '/ckeditor5/engine/treeview/element.js';
+import AttributeElement from '/ckeditor5/engine/treeview/attributeelement.js';
+import ContainerElement from '/ckeditor5/engine/treeview/containerelement.js';
+import Text from '/ckeditor5/engine/treeview/text.js';
+import Selection from '/ckeditor5/engine/treeview/selection.js';
+import Range from '/ckeditor5/engine/treeview/range.js';
+
+describe( 'view test utils', () => {
+	describe( 'getData', () => {
+		it( 'should write elements and texts', () => {
+			const text = new Text( 'foobar' );
+			const b = new Element( 'b', null, text );
+			const p = new Element( 'p', null, b );
+
+			expect( getData( p ) ).to.equal( '<p><b>foobar</b></p>' );
+		} );
+
+		it( 'should write elements with attributes', () => {
+			const text = new Text( 'foobar' );
+			const b = new Element( 'b', {
+				foo: 'bar'
+			}, text );
+			const p = new Element( 'p', {
+				baz: 'qux',
+				bar: 'taz',
+				class: 'short wide'
+			}, b );
+
+			expect( getData( p ) ).to.equal( '<p class="short wide" baz="qux" bar="taz"><b foo="bar">foobar</b></p>' );
+		} );
+
+		it( 'should write selection ranges inside elements', () => {
+			const text1 = new Text( 'foobar' );
+			const text2 = new Text( 'bazqux' );
+			const b1 = new Element( 'b', null, text1 );
+			const b2 = new Element( 'b', null, text2 );
+			const p = new Element( 'p', null, [ b1, b2 ] );
+			const range = Range.createFromParentsAndOffsets( p, 1, p, 2 );
+			const selection = new Selection();
+			selection.addRange( range );
+			expect( getData( p, selection ) ).to.equal( '<p><b>foobar</b>[<b>bazqux</b>]</p>' );
+		} );
+
+		it( 'should write collapsed selection ranges inside elements', () => {
+			const text = new Text( 'foobar' );
+			const p = new Element( 'p', null, text );
+			const range = Range.createFromParentsAndOffsets( p, 0, p, 0 );
+			const selection = new Selection();
+			selection.addRange( range );
+			expect( getData( p, selection ) ).to.equal( '<p>[]foobar</p>' );
+		} );
+
+		it( 'should write selection ranges inside text', () => {
+			const text1 = new Text( 'foobar' );
+			const text2 = new Text( 'bazqux' );
+			const b1 = new Element( 'b', null, text1 );
+			const b2 = new Element( 'b', null, text2 );
+			const p = new Element( 'p', null, [ b1, b2 ] );
+			const range = Range.createFromParentsAndOffsets( text1, 1, text1, 5 );
+			const selection = new Selection();
+			selection.addRange( range );
+			expect( getData( p, selection ) ).to.equal( '<p><b>f{ooba}r</b><b>bazqux</b></p>' );
+		} );
+
+		it( 'should write ranges that start inside text end ends between elements', () => {
+			const text1 = new Text( 'foobar' );
+			const text2 = new Text( 'bazqux' );
+			const b1 = new Element( 'b', null, text1 );
+			const b2 = new Element( 'b', null, text2 );
+			const p = new Element( 'p', null, [ b1, b2 ] );
+			const range = Range.createFromParentsAndOffsets( p, 0, text2, 5 );
+			const selection = new Selection();
+			selection.addRange( range );
+			expect( getData( p, selection ) ).to.equal( '<p>[<b>foobar</b><b>bazqu}x</b></p>' );
+		} );
+
+		it( 'should write elements types as namespaces when needed', () => {
+			const text = new Text( 'foobar' );
+			const b = new AttributeElement( 'b', null, text );
+			const p = new ContainerElement( 'p', null, b );
+
+			expect( getData( p, null, { showTypes: true } ) )
+				.to.equal( '<container:p><attribute:b>foobar</attribute:b></container:p>' );
+		} );
+	} );
+} );

+ 160 - 0
packages/ckeditor5-engine/tests/_utils/view.js

@@ -0,0 +1,160 @@
+/**
+ * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+/* jshint latedef:false */
+
+import Element from '/ckeditor5/engine/treeview/element.js';
+import AttributeElement from '/ckeditor5/engine/treeview/attributeelement.js';
+import ContainerElement from '/ckeditor5/engine/treeview/containerelement.js';
+import Text from '/ckeditor5/engine/treeview/text.js';
+
+const ELEMENT_RANGE_START_TOKEN = '[';
+const ELEMENT_RANGE_END_TOKEN = ']';
+const TEXT_RANGE_START_TOKEN = '{';
+const TEXT_RANGE_END_TOKEN = '}';
+
+export function getData( root, selection, options ) {
+	const viewStringify = new ViewStringify( root, selection, options );
+
+	return viewStringify.stringify();
+}
+
+class ViewStringify {
+	constructor( root, selection, options = {} ) {
+		this.root = root;
+		this.selection = null;
+		this.ranges = [];
+
+		if ( selection ) {
+			this.selection = selection;
+			this.ranges = [ ...selection.getRanges() ];
+		}
+
+		this.showTypes = !!options.showTypes;
+		this.showPriorities = !!options.showPriorities;
+	}
+
+	/**
+	 * Converts view to string.
+	 *
+	 * @returns {string} String representation of the view elements.
+	 */
+	stringify() {
+		let result = '';
+		this._walkView( this.root, ( chunk ) => {
+			result += chunk;
+		} );
+
+		return result;
+	}
+
+	_walkView( root, cb ) {
+		if ( root instanceof Element ) {
+			cb( this._stringifyElementOpen( root ) );
+
+			let offset = 0;
+			this._checkElementRanges( root, offset, cb );
+
+			for ( let child of root.getChildren() ) {
+				this._walkView( child, cb );
+				offset++;
+				this._checkElementRanges( root, offset, cb );
+			}
+
+			cb( this._stringifyElementClose( root ) );
+		}
+
+		if ( root instanceof Text ) {
+			cb( this._checkTextRanges( root ) );
+		}
+	}
+
+	/**
+	 * Checks if given {@link engine.treeView.Element Element} has {@link engine.treeView.Range#start range start} or
+	 * {@link engine.treeView.Range#start range end} placed at given offset. Calls `callback` function each time range
+	 * start or end is found.
+	 *
+	 * @private
+	 * @param {engine.treeView.Element} element
+	 * @param {Number} offset
+	 * @param {Function} callback
+	 */
+	_checkElementRanges( element, offset, callback ) {
+		for ( let range of this.ranges ) {
+			if ( range.start.parent == element && range.start.offset === offset ) {
+				callback( ELEMENT_RANGE_START_TOKEN );
+			}
+
+			if ( range.end.parent === element && range.end.offset === offset ) {
+				callback( ELEMENT_RANGE_END_TOKEN );
+			}
+		}
+	}
+
+	/**
+	 * Checks if given {@link engine.treeView.Element Text node} has {@link engine.treeView.Range#start range start} or
+	 * {@link engine.treeView.Range#start range end} placed somewhere inside. Returns string representation of text
+	 * with range delimiters placed inside.
+	 *
+	 * @private
+	 * @param {engine.treeView.Text} textNode
+	 */
+	_checkTextRanges( textNode ) {
+		let result = textNode.data;
+		let textOffset = 0;
+
+		for ( let range of this.ranges ) {
+			if ( range.start.parent == textNode ) {
+				result = ViewStringify._insertToString( result, range.start.offset + textOffset, TEXT_RANGE_START_TOKEN );
+				textOffset++;
+			}
+
+			if ( range.end.parent == textNode && !range.isCollapsed ) {
+				result = ViewStringify._insertToString( result, range.end.offset + textOffset, TEXT_RANGE_END_TOKEN );
+				textOffset++;
+			}
+		}
+
+		return result;
+	}
+
+	_stringifyElementOpen( element ) {
+		let attributes = [];
+		const namespace = this._stringifyElementType( element );
+
+		// TODO: Maybe attributes should be put in alphabetical order, it might be easier to write expected string.
+		for ( let attribute of element.getAttributeKeys() ) {
+			attributes.push( `${ attribute }="${ element.getAttribute( attribute ) }"` );
+		}
+
+		return `<${ namespace + element.name }${ attributes.length > 0 ? ' ' + attributes.join( ' ' ) : '' }>`;
+	}
+
+	_stringifyElementClose( element ) {
+		const namespace = this._stringifyElementType( element );
+
+		return `</${ namespace + element.name }>`;
+	}
+
+	_stringifyElementType( element ) {
+		if ( this.showTypes ) {
+			if ( element instanceof AttributeElement ) {
+				return 'attribute:';
+			}
+
+			if ( element instanceof ContainerElement ) {
+				return 'container:';
+			}
+		}
+
+		return '';
+	}
+
+	static _insertToString( source, index, insert ) {
+		return source.substr( 0, index ) + insert + source.substr( index );
+	}
+}