浏览代码

Added parsing values of model element attributes + little test cleanup.

Oskar Wrobel 9 年之前
父节点
当前提交
1bdb4c0f9d

+ 48 - 31
packages/ckeditor5-engine/tests/_utils-tests/model.js

@@ -56,13 +56,6 @@ describe( 'model test utils', () => {
 			sinon.assert.calledWithExactly( stringifySpy, root, document.selection );
 		} );
 
-		it( 'should support unicode', () => {
-			root.appendChildren( 'நிலைக்கு' );
-			document.selection.addRange( Range.createFromParentsAndOffsets( root, 2, root, 6 ) );
-
-			expect( getData( document ) ).to.equal( 'நி[லைக்]கு' );
-		} );
-
 		it( 'should throw an error when passing invalid document', () => {
 			expect( () => {
 				getData( { invalid: 'document' } );
@@ -117,6 +110,10 @@ describe( 'model test utils', () => {
 			test( 'this is [test text]' );
 		} );
 
+		it( 'should insert unicode text with selection', () => {
+			test( 'நி[லைக்]கு' );
+		} );
+
 		it( 'should insert element', () => {
 			test( '<b>foo bar</b>', '[]<b>foo bar</b>' );
 		} );
@@ -140,10 +137,6 @@ describe( 'model test utils', () => {
 			expect( document.selection.isBackward ).to.true;
 		} );
 
-		it( 'should support unicode', () => {
-			test( 'நி[லைக்]கு' );
-		} );
-
 		it( 'should throw an error when passing invalid document', () => {
 			expect( () => {
 				setData( { invalid: 'document' } );
@@ -161,6 +154,7 @@ describe( 'model test utils', () => {
 	describe( 'stringify', () => {
 		it( 'should stringify text', () => {
 			const text = new Text( 'text', { underline: true, bold: true } );
+
 			expect( stringify( text ) ).to.equal( '<$text bold="true" underline="true">text</$text>' );
 		} );
 
@@ -201,7 +195,7 @@ describe( 'model test utils', () => {
 		it( 'writes element attributes', () => {
 			root.appendChildren(
 				new Element( 'a', { foo: true, bar: 1, car: false }, [
-					new Element( 'b', { fooBar: 'x y', barFoo: JSON.stringify( { x: 1, y: 2 } ) } )
+					new Element( 'b', { fooBar: 'x y', barFoo: { x: 1, y: 2 } } )
 				] )
 			);
 
@@ -229,6 +223,12 @@ describe( 'model test utils', () => {
 			);
 		} );
 
+		it( 'writes unicode text', () => {
+			root.appendChildren( new Text( 'நிலைக்கு' ) );
+
+			expect( stringify( root ) ).to.equal( 'நிலைக்கு' );
+		} );
+
 		describe( 'selection', () => {
 			let elA, elB;
 
@@ -253,10 +253,6 @@ describe( 'model test utils', () => {
 				);
 			} );
 
-			it( 'writes only requested element', () => {
-				expect( stringify( elA ) ).to.equal( '<a></a>' );
-			} );
-
 			it( 'writes selection collapsed in an element', () => {
 				selection.collapse( root );
 
@@ -287,8 +283,6 @@ describe( 'model test utils', () => {
 				expect( stringify( root, selection ) ).to.equal(
 					'<a></a>foo<$text bold="true">bar[]</$text><b></b>'
 				);
-				expect( selection.getAttribute( 'bold' ) ).to.true;
-				expect( count( selection.getAttributes() ) ).to.equal( 1 );
 			} );
 
 			it( 'writes selection collapsed at the end of the root', () => {
@@ -308,8 +302,6 @@ describe( 'model test utils', () => {
 				expect( stringify( root, selection ) ).to.equal(
 					'<a></a>foo<$text bold="true">b[]ar</$text><b></b>'
 				);
-				expect( selection.getAttribute( 'bold' ) ).to.true;
-				expect( count( selection.getAttributes() ) ).to.equal( 1 );
 			} );
 
 			it( 'writes flat selection containing couple of nodes', () => {
@@ -353,6 +345,15 @@ describe( 'model test utils', () => {
 				);
 			} );
 
+			it( 'writes selection in unicode text', () => {
+				const root = document.createRoot( '$root', 'empty' );
+
+				root.appendChildren( new Text( 'நிலைக்கு' ) );
+				selection.addRange( Range.createFromParentsAndOffsets( root, 2, root, 6 ) );
+
+				expect( stringify( root, selection ) ).to.equal( 'நி[லைக்]கு' );
+			} );
+
 			it( 'uses range and coverts it to selection', () => {
 				const range = Range.createFromParentsAndOffsets( elA, 0, elB, 0 );
 
@@ -416,25 +417,41 @@ describe( 'model test utils', () => {
 			data: 'foo<a>bar</a>bom'
 		} );
 
+		test( 'creates text nodes with unicode text', {
+			data: 'நிலைக்கு'
+		} );
+
 		test( 'sets elements attributes', {
-			data: '<a foo="1" bar="true" car="x y"><b x="y"></b></a>',
-			output: '<a bar="true" car="x y" foo="1"><b x="y"></b></a>',
-			check( a ) {
-				expect( a.getAttribute( 'bar' ) ).to.equal( 'true' );
-				expect( a.getAttribute( 'car' ) ).to.equal( 'x y' );
-				expect( a.getAttribute( 'foo' ) ).to.equal( '1' );
+			data: '<a bar="true" car="x y" foo="1"></a><b x="y"></b>',
+			output: '<a bar="true" car="x y" foo="1"></a><b x="y"></b>',
+			check( root ) {
+				expect( root.getChild( 0 ).getAttribute( 'bar' ) ).to.equal( true );
+				expect( root.getChild( 0 ).getAttribute( 'car' ) ).to.equal( 'x y' );
+				expect( root.getChild( 0 ).getAttribute( 'foo' ) ).to.equal( 1 );
+				expect( root.getChild( 1 ).getAttribute( 'x' ) ).to.equal( 'y' );
 			}
 		} );
 
 		test( 'sets text attributes', {
-			data: '<$text bold="true" italic="true">foo</$text><$text bold="true">bar</$text>bom',
+			data: '<$text bar="true" car="x y" foo="1">foo</$text><$text x="y">bar</$text>bom',
 			check( root ) {
 				expect( root.childCount ).to.equal( 3 );
 				expect( root.maxOffset ).to.equal( 9 );
-				expect( root.getChild( 0 ) ).to.have.property( 'data', 'foo' );
-				expect( root.getChild( 0 ).getAttribute( 'italic' ) ).to.equal( 'true' );
-				expect( root.getChild( 1 ) ).to.have.property( 'data', 'bar' );
-				expect( root.getChild( 1 ).getAttribute( 'bold' ) ).to.equal( 'true' );
+				expect( root.getChild( 0 ).getAttribute( 'bar' ) ).to.equal( true );
+				expect( root.getChild( 0 ).getAttribute( 'car' ) ).to.equal( 'x y' );
+				expect( root.getChild( 0 ).getAttribute( 'foo' ) ).to.equal( 1 );
+				expect( root.getChild( 1 ).getAttribute( 'x' ) ).to.equal( 'y' );
+				expect( count( root.getChild( 2 ).getAttributes() ) ).to.equal( 0 );
+			}
+		} );
+
+		test( 'creates element with complex attributes', {
+			data: `<a foo='{"x":1,"y":2}'></a>`,
+			output: '<a foo="{"x":1,"y":2}"></a>',
+			check( a ) {
+				expect( count( a.getAttributes() ) ).to.equal( 1 );
+				expect( a.getAttribute( 'foo' ) ).to.have.property( 'x', 1 );
+				expect( a.getAttribute( 'foo' ) ).to.have.property( 'y', 2 );
 			}
 		} );
 

+ 59 - 13
packages/ckeditor5-engine/tests/_utils/model.js

@@ -3,15 +3,6 @@
  * For licensing, see LICENSE.md.
  */
 
-import Mapper from '/ckeditor5/engine/conversion/mapper.js';
-import { parse as viewParse, stringify as viewStringify } from '/tests/engine/_utils/view.js';
-import {
-	convertRangeSelection,
-	convertCollapsedSelection,
-	convertSelectionAttribute
-} from '/ckeditor5/engine/conversion/model-selection-to-view-converters.js';
-import { insertText, insertElement, wrap } from '/ckeditor5/engine/conversion/model-to-view-converters.js';
-
 import RootElement from '/ckeditor5/engine/model/rootelement.js';
 import ModelDocument from '/ckeditor5/engine/model/document.js';
 import ModelRange from '/ckeditor5/engine/model/range.js';
@@ -30,6 +21,16 @@ import ViewDocumentFragment from '/ckeditor5/engine/view/documentfragment.js';
 import ViewElement from '/ckeditor5/engine/view/containerelement.js';
 import ViewAttributeElement from '/ckeditor5/engine/view/attributeelement.js';
 
+import Mapper from '/ckeditor5/engine/conversion/mapper.js';
+import { parse as viewParse, stringify as viewStringify } from '/tests/engine/_utils/view.js';
+import {
+	convertRangeSelection,
+	convertCollapsedSelection,
+	convertSelectionAttribute
+} from '/ckeditor5/engine/conversion/model-selection-to-view-converters.js';
+import { insertText, insertElement, wrap } from '/ckeditor5/engine/conversion/model-to-view-converters.js';
+import isPlainObject from '/ckeditor5/utils/lib/lodash/isplainobject.js';
+
 /**
  * Writes the contents of the {@link engine.model.Document Document} to an HTML-like string.
  *
@@ -199,10 +200,15 @@ export function stringify( node, selectionOrPositionOrRange = null ) {
 	modelToView.on( 'insert:$text', insertText() );
 	modelToView.on( 'addAttribute', wrap( ( value, data ) => {
 		if ( data.item instanceof ModelTextProxy ) {
-			return new ViewAttributeElement( 'model-text-with-attributes', { [ data.attributeKey ]: value } );
+			return new ViewAttributeElement( 'model-text-with-attributes', { [ data.attributeKey ]: stringifyObject( value ) } );
 		}
 	} ) );
-	modelToView.on( 'insert', insertElement( data => new ViewElement( data.item.name, data.item.getAttributes() )  ) );
+	modelToView.on( 'insert', insertElement( ( data ) => {
+		// Stringify object types values for properly display as an output string.
+		const attributes = parseAttributes( data.item.getAttributes(), stringifyObject );
+
+		return new ViewElement( data.item.name, attributes );
+	} ) );
 	modelToView.on( 'selection', convertRangeSelection() );
 	modelToView.on( 'selection', convertCollapsedSelection() );
 	modelToView.on( 'selectionAttribute', convertSelectionAttribute( ( value, data ) => {
@@ -330,7 +336,11 @@ function convertToModelElement() {
 			throw new Error( `Element '${ schemaQuery.name }' not allowed in context.` );
 		}
 
-		data.output = new ModelElement( data.input.name, data.input.getAttributes() );
+		// View attribute value is a string so we want to typecast it to the original type.
+		// E.g. `bold="true"` - value will be parsed from string `"true"` to boolean `true`.
+		const attributes = parseAttributes( data.input.getAttributes(), parseValue );
+
+		data.output = new ModelElement( data.input.name, attributes );
 		conversionApi.mapper.bindElements( data.output, data.input );
 
 		data.context.push( data.output );
@@ -355,7 +365,11 @@ function convertToModelText( withAttributes = false ) {
 		let node;
 
 		if ( withAttributes ) {
-			node = new ModelText( data.input.getChild( 0 ).data, data.input.getAttributes() );
+			// View attribute value is a string so we want to typecast it to the original type.
+			// E.g. `bold="true"` - value will be parsed from string `"true"` to boolean `true`.
+			const attributes = parseAttributes( data.input.getAttributes(), parseValue );
+
+			node = new ModelText( data.input.getChild( 0 ).data, attributes );
 		} else {
 			node = new ModelText( data.input.data );
 		}
@@ -365,3 +379,35 @@ function convertToModelText( withAttributes = false ) {
 		evt.stop();
 	};
 }
+
+// Typecast string value to the original type.
+// 		`'true'` => `true`
+// 		`'1'` => `1`
+//		`'{"x":1,"y":2}'` => `{ x: 1, y: 2 }`
+function parseValue( attribute ) {
+	try {
+		return JSON.parse( attribute );
+	} catch ( e ) {
+		return attribute;
+	}
+}
+
+// When value is an Object stringify it.
+function stringifyObject( data ) {
+	if ( isPlainObject( data ) ) {
+		return JSON.stringify( data );
+	}
+
+	return data;
+}
+
+// Loop trough attributes map and parse each value by passed parser.
+function parseAttributes( attributes, parser ) {
+	let result = new Map();
+
+	for ( let attribute of attributes ) {
+		result.set( attribute[ 0 ], parser( attribute[ 1 ] ) );
+	}
+
+	return result;
+}

+ 2 - 2
packages/ckeditor5-engine/tests/model/composer/deletecontents.js

@@ -95,7 +95,7 @@ describe( 'Delete utils', () => {
 				deleteContents( document.batch(), document.selection );
 
 				expect( getData( document ) ).to.equal( '<$text bold="true">fo[]</$text>ar' );
-				expect( document.selection.getAttribute( 'bold' ) ).to.equal( 'true' );
+				expect( document.selection.getAttribute( 'bold' ) ).to.equal( true );
 			} );
 
 			it( 'deletes characters (2nd half has attrs)', () => {
@@ -128,7 +128,7 @@ describe( 'Delete utils', () => {
 				deleteContents( document.batch(), document.selection );
 
 				expect( getData( document ) ).to.equal( '<p>x<$text bold="true">a[]b</$text>y</p>' );
-				expect( document.selection.getAttribute( 'bold' ) ).to.equal( 'true' );
+				expect( document.selection.getAttribute( 'bold' ) ).to.equal( true );
 			} );
 		} );
 

+ 1 - 1
packages/ckeditor5-engine/tests/model/composer/modifyselection.js

@@ -304,7 +304,7 @@ describe( 'Delete utils', () => {
 					modifySelection( document.selection, { direction: 'backward' } );
 
 					expect( stringify( document.getRoot(), document.selection ) ).to.equal( '<p><$text bold="true">foo[]</$text>b</p>' );
-					expect( document.selection.getAttribute( 'bold' ) ).to.equal( 'true' );
+					expect( document.selection.getAttribute( 'bold' ) ).to.equal( true );
 				} );
 			} );
 		} );