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

Removed treeModel.Attribute, treeModel.AttributeList. Fixes in files to use new Attribute API. Fixed bug in AttributeOperation by AttributeOperation transformation.

Szymon Cofalik 10 лет назад
Родитель
Сommit
7361a2d694
30 измененных файлов с 392 добавлено и 1102 удалено
  1. 0 80
      packages/ckeditor5-engine/src/treemodel/attribute.js
  2. 0 155
      packages/ckeditor5-engine/src/treemodel/attributelist.js
  3. 5 15
      packages/ckeditor5-engine/src/treemodel/delta/attributedelta.js
  4. 1 2
      packages/ckeditor5-engine/src/treemodel/delta/weakinsertdelta.js
  5. 5 4
      packages/ckeditor5-engine/src/treemodel/document.js
  6. 13 10
      packages/ckeditor5-engine/src/treemodel/element.js
  7. 17 24
      packages/ckeditor5-engine/src/treemodel/node.js
  8. 9 9
      packages/ckeditor5-engine/src/treemodel/nodelist.js
  9. 32 40
      packages/ckeditor5-engine/src/treemodel/operation/attributeoperation.js
  10. 8 43
      packages/ckeditor5-engine/src/treemodel/operation/transform.js
  11. 24 34
      packages/ckeditor5-engine/src/treemodel/selection.js
  12. 11 8
      packages/ckeditor5-engine/src/treemodel/text.js
  13. 10 62
      packages/ckeditor5-engine/src/treemodel/textfragment.js
  14. 0 28
      packages/ckeditor5-engine/tests/treemodel/attribute.js
  15. 0 190
      packages/ckeditor5-engine/tests/treemodel/attributelist.js
  16. 3 3
      packages/ckeditor5-engine/tests/treemodel/characterproxy.js
  17. 15 16
      packages/ckeditor5-engine/tests/treemodel/delta/attributedelta.js
  18. 3 4
      packages/ckeditor5-engine/tests/treemodel/delta/mergedelta.js
  19. 5 6
      packages/ckeditor5-engine/tests/treemodel/delta/splitdelta.js
  20. 1 5
      packages/ckeditor5-engine/tests/treemodel/delta/weakinsertdelta.js
  21. 18 13
      packages/ckeditor5-engine/tests/treemodel/document/change-event.js
  22. 14 23
      packages/ckeditor5-engine/tests/treemodel/element.js
  23. 8 33
      packages/ckeditor5-engine/tests/treemodel/node.js
  24. 17 20
      packages/ckeditor5-engine/tests/treemodel/nodelist.js
  25. 75 116
      packages/ckeditor5-engine/tests/treemodel/operation/attributeoperation.js
  26. 65 49
      packages/ckeditor5-engine/tests/treemodel/operation/transform.js
  27. 20 46
      packages/ckeditor5-engine/tests/treemodel/selection.js
  28. 3 6
      packages/ckeditor5-engine/tests/treemodel/text.js
  29. 5 50
      packages/ckeditor5-engine/tests/treemodel/textfragment.js
  30. 5 8
      packages/ckeditor5-engine/tests/treemodel/treewalker.js

+ 0 - 80
packages/ckeditor5-engine/src/treemodel/attribute.js

@@ -1,80 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-'use strict';
-
-import langUtils from '../lib/lodash/lang.js';
-
-/**
- * Attributes can store any additional information for nodes in the data model.
- *
- * @class treeModel.Attribute
- */
-export default class Attribute {
-	/**
-	 * Creates a new instance of the `Attribute` class. Once attribute is created it is immutable.
-	 *
-	 * @param {String} key Attribute key.
-	 * @param {*} value Attribute value.
-	 * @constructor
-	 */
-	constructor( key, value ) {
-		/**
-		 * Attribute key.
-		 *
-		 * @readonly
-		 * @property {String} key
-		 */
-		this.key = key;
-
-		/**
-		 * Attribute value. Note that value may be any type, including objects.
-		 *
-		 * @readonly
-		 * @property {*} value
-		 */
-		this.value = value;
-
-		/**
-		 * Attribute hash. Used to compare attributes. Two attributes with the same key and value will have the same hash.
-		 *
-		 * @readonly
-		 * @private
-		 * @property {String} _hash
-		 */
-		this._hash = this.key + ': ' + JSON.stringify( this.value, sort );
-
-		// We do not care about the order, so collections with the same elements should return the same hash.
-		function sort( key, value ) {
-			if ( !langUtils.isArray( value ) && langUtils.isObject( value ) ) {
-				const sorted = {};
-
-				// Sort keys and fill up the sorted object.
-				Object.keys( value ).sort().forEach( ( key ) => {
-					sorted[ key ] = value[ key ];
-				} );
-
-				return sorted;
-			} else {
-				return value;
-			}
-		}
-	}
-
-	/**
-	 * Compares two attributes. Returns `true` if two attributes have the same key and value even if the order of keys
-	 * in the value object is different.
-	 *
-	 *		let attr1 = new Attribute( 'foo', { a: 1, b: 2 } );
-	 *		let attr2 = new Attribute( 'foo', { b: 2, a: 1 } );
-	 *		attr1.isEqual( attr2 ); // true
-	 *
-	 * @param {treeModel.Attribute} otherAttr Attribute to compare with.
-	 * @returns {Boolean} True if attributes are equal to each other.
-	 */
-	isEqual( otherAttr ) {
-		return this._hash === otherAttr._hash;
-	}
-}

+ 0 - 155
packages/ckeditor5-engine/src/treemodel/attributelist.js

@@ -1,155 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-'use strict';
-
-import Attribute from './attribute.js';
-
-/**
- * List of attributes.
- *
- * @class treeModel.AttributeList
- */
-export default class AttributeList extends Map {
-	/**
-	 * Creates AttributeList. If parameter is passed, initializes created list with passed {@link treeModel.Attribute}s.
-	 *
-	 * @constructor
-	 * @param {Iterable.<treeModel.Attribute>} attrs Attributes to initialize with.
-	 */
-	constructor( attrs ) {
-		super();
-
-		if ( attrs ) {
-			this.setTo( attrs );
-		}
-
-		/**
-		 * Amount of attributes added to the AttributeList.
-		 *
-		 * @property {Number} size
-		 */
-	}
-
-	/**
-	 * AttributeList iterator. Iterates over all attributes from the list.
-	 */
-	[ Symbol.iterator ]() {
-		let it = super[ Symbol.iterator ]();
-
-		return {
-			next: () => {
-				let step = it.next();
-
-				return {
-					value: step.value ? step.value[ 1 ] : undefined,
-					done: step.done
-				};
-			}
-		};
-	}
-
-	/**
-	 * Adds attribute to the attributes list. If attribute with the same key already is set, it overwrites its values.
-	 *
-	 * @chainable
-	 * @param {treeModel.Attribute} attr Attribute to add or overwrite.
-	 * @returns {treeModel.AttributeList} This AttributeList object.
-	 */
-	set( attr ) {
-		super.set( attr.key, attr );
-
-		return this;
-	}
-
-	/**
-	 * Removes all attributes from AttributeList and adds given attributes.
-	 *
-	 * @param {Iterable.<Attribute>} attrs Iterable object containing attributes to be set.
-	 */
-	setTo( attrs ) {
-		this.clear();
-
-		for ( let value of attrs ) {
-			this.set( value );
-		}
-	}
-
-	/**
-	 * Checks if AttributeList contains attribute {@link treeModel.Attribute#isEqual equal} to given attribute or
-	 * attribute with given key if string was passed.
-	 *
-	 * @param {treeModel.Attribute|String} attrOrKey Attribute or key of attribute to check.
-	 * @returns {Boolean} `true` if given attribute or attribute with given key exists in AttributeList. `false` otherwise.
-	 */
-	has( attrOrKey ) {
-		if ( attrOrKey instanceof Attribute ) {
-			let attr = this.get( attrOrKey.key );
-
-			if ( attr ) {
-				return attr.isEqual( attrOrKey );
-			}
-		} else {
-			return super.has( attrOrKey );
-		}
-
-		return false;
-	}
-
-	/**
-	 * Gets an attribute value by attribute key.
-	 *
-	 * @param {String} key Key of attribute to look for.
-	 * @returns {*} Value of attribute with given key or null if the attribute has not been found in AttributeList
-	 */
-	getValue( key ) {
-		let attr = this.get( key );
-
-		return attr ? attr.value : null;
-	}
-
-	/**
-	 * Checks whether this AttributeList has exactly same attributes as given one.
-	 *
-	 * @param {treeModel.AttributeList} attrs AttributeList to compare with.
-	 * @returns {Boolean} `true` if AttributeLists are equal, `false` otherwise.
-	 */
-	isEqual( attrs ) {
-		if ( this.size != attrs.size ) {
-			return false;
-		}
-
-		for ( let attr of attrs ) {
-			if ( !this.has( attr ) ) {
-				return false;
-			}
-		}
-
-		return true;
-	}
-
-	/**
-	 * Gets an attribute by its key.
-	 *
-	 * @method get
-	 * @param {String} key Key of attribute to look for.
-	 * @returns {treeModel.Attribute|null} Attribute with given key or null if the attribute has not been found in
-	 * AttributeList.
-	 */
-
-	/**
-	 * Removes an attribute with given key from AttributeList.
-	 *
-	 * @method delete
-	 * @param {String} key Key of attribute to remove.
-	 * @returns {Boolean} `true` if the attribute existed in the AttributeList. `false` otherwise.
-	 */
-
-	/**
-	 * Removes all attributes from AttributeList.
-	 *
-	 * @method clear
-	 */
-}

+ 5 - 15
packages/ckeditor5-engine/src/treemodel/delta/attributedelta.js

@@ -9,7 +9,6 @@ import { register } from '../batch-base.js';
 import AttributeOperation from '../operation/attributeoperation.js';
 import Position from '../position.js';
 import Range from '../range.js';
-import Attribute from '../attribute.js';
 import Element from '../element.js';
 
 /**
@@ -65,7 +64,7 @@ function attribute( batch, key, value, nodeOrRange ) {
 }
 
 function changeNode( doc, delta, key, value, node ) {
-	const previousValue = node.getAttributeValue( key );
+	const previousValue = node.getAttribute( key );
 	let range;
 
 	if ( previousValue != value ) {
@@ -79,12 +78,7 @@ function changeNode( doc, delta, key, value, node ) {
 			range = new Range( Position.createBefore( node ), Position.createAfter( node ) );
 		}
 
-		const operation = new AttributeOperation(
-				range,
-				previousValue ? new Attribute( key, previousValue ) : null,
-				value ? new Attribute( key, value ) : null,
-				doc.version
-			);
+		const operation = new AttributeOperation( range, key, previousValue, value, doc.version );
 
 		doc.applyOperation( operation );
 		delta.addOperation( operation );
@@ -114,7 +108,7 @@ function changeRange( doc, delta, key, value, range ) {
 		// We check values only when the range contains given element, that is when the iterator "enters" the element.
 		// To prevent double-checking or not needed checking, we filter-out iterator values for ELEMENT_END position.
 		if ( next.value.type != 'ELEMENT_END' ) {
-			valueAfter = next.value.item.getAttributeValue( key );
+			valueAfter = next.value.item.getAttribute( key );
 
 			// At the first run of the iterator the position in undefined. We also do not have a valueBefore, but
 			// because valueAfter may be null, valueBefore may be equal valueAfter ( undefined == null ).
@@ -141,12 +135,8 @@ function changeRange( doc, delta, key, value, range ) {
 	}
 
 	function addOperation() {
-		const operation = new AttributeOperation(
-				new Range( lastSplitPosition, position ),
-				valueBefore ? new Attribute( key, valueBefore ) : null,
-				value ? new Attribute( key, value ) : null,
-				doc.version
-			);
+		let range = new Range( lastSplitPosition, position );
+		const operation = new AttributeOperation( range, key, valueBefore, value, doc.version );
 
 		doc.applyOperation( operation );
 		delta.addOperation( operation );

+ 1 - 2
packages/ckeditor5-engine/src/treemodel/delta/weakinsertdelta.js

@@ -34,7 +34,6 @@ export default class WeakInsertDelta extends Delta {}
  * @method weakInsert
  * @param {treeModel.Position} position Position of insertion.
  * @param {treeModel.NodesSet} nodes The list of nodes to be inserted.
- * List of nodes can be of any type accepted by the {@link treeModel.NodeList} constructor.
  */
 register( 'weakInsert', function( position, nodes ) {
 	const delta = new WeakInsertDelta();
@@ -42,7 +41,7 @@ register( 'weakInsert', function( position, nodes ) {
 	nodes = new NodeList( nodes );
 
 	for ( let node of nodes._nodes ) {
-		node._attrs.setTo( this.doc.selection._attrs );
+		node._attrs = new Map( this.doc.selection.getAttributes() );
 	}
 
 	const operation = new InsertOperation( position, nodes, this.doc.version );

+ 5 - 4
packages/ckeditor5-engine/src/treemodel/document.js

@@ -215,10 +215,11 @@ export default class Document {
 	 * {@link #graveyard graveyard root}.
 	 * @param {treeModel.Position} [changeInfo.sourcePosition] Change source position. Exists for 'remove', 'reinsert' and 'move'.
 	 * Note that for 'reinsert' the source position will be in the {@link #graveyard graveyard root}.
-	 * @param {treeModel.Attribute} [changeInfo.oldAttr] Only for 'attr' type. If the type is 'attr' and `oldAttr`
-	 * is `undefined` it means that new attribute was inserted. Otherwise it contains changed or removed attribute.
-	 * @param {treeModel.Attribute} [changeInfo.newAttr] Only for 'attr' type. If the type is 'attr' and `newAttr`
-	 * is `undefined` it means that attribute was removed. Otherwise it contains changed or inserted attribute.
+	 * @param {*} [changeInfo.key] Only for 'attr' type. Key of changed / inserted / removed attribute.
+	 * @param {*} [changeInfo.oldValue] Only for 'attr' type. If the type is 'attr' and `oldValue`
+	 * is `undefined` it means that new attribute was inserted. Otherwise it contains changed or removed attribute value.
+	 * @param {*} [changeInfo.newValue] Only for 'attr' type. If the type is 'attr' and `newValue`
+	 * is `undefined` it means that attribute was removed. Otherwise it contains changed or inserted attribute value.
 	 * @param {treeModel.Batch} {@link treeModel.Batch} of changes which this change is a part of.
 	 */
 

+ 13 - 10
packages/ckeditor5-engine/src/treemodel/element.js

@@ -7,6 +7,8 @@
 
 import Node from './node.js';
 import NodeList from './nodelist.js';
+import langUtils from '../lib/lodash/lang.js';
+import utils from '../utils.js';
 
 /**
  * Tree data model element.
@@ -18,7 +20,7 @@ export default class Element extends Node {
 	 * Creates a tree data model element.
 	 *
 	 * @param {String} name Node name.
-	 * @param {Iterable} attrs Iterable collection of {@link treeModel.Attribute attributes}.
+	 * @param {Iterable} attrs Iterable collection of attributes.
 	 * @param {treeModel.NodesSet} children List of nodes to be inserted
 	 * into created element. List of nodes can be of any type accepted by the {@link treeModel.NodeList} constructor.
 	 * @constructor
@@ -119,23 +121,24 @@ export default class Element extends Node {
 	/**
 	 * Sets attribute on the element. If attribute with the same key already is set, it overwrites its values.
 	 *
-	 * @chainable
-	 * @param {treeModel.Attribute} attr Attribute to set or overwrite with.
-	 * @returns {treeModel.Element} This element.
+	 * @param {String} key Key of attribute to set.
+	 * @param {*} value Attribute value.
 	 */
-	setAttribute( attr ) {
-		this._attrs.set( attr );
-
-		return this;
+	setAttribute( key, value ) {
+		this._attrs.set( key, value );
 	}
 
 	/**
 	 * Removes all attributes from the element and sets given attributes.
 	 *
-	 * @param {Iterable.<treeModel.Attribute>} attrs Iterable object containing {@link treeModel.Attribute attributes} to be set.
+	 * @param {Iterable.<*>} attrs Iterable object containing attributes to be set. See {@link treeModel.Node#getAttributes}.
 	 */
 	setAttributesTo( attrs ) {
-		this._attrs.setTo( attrs );
+		if ( langUtils.isPlainObject( attrs ) ) {
+			this._attrs = utils.objectToMap( attrs );
+		} else {
+			this._attrs = new Map( attrs );
+		}
 	}
 
 	/**

+ 17 - 24
packages/ckeditor5-engine/src/treemodel/node.js

@@ -5,8 +5,8 @@
 
 'use strict';
 
-import AttributeList from './attributelist.js';
 import langUtils from '../lib/lodash/lang.js';
+import utils from '../utils.js';
 import CKEditorError from '../ckeditorerror.js';
 
 /**
@@ -21,7 +21,7 @@ export default class Node {
 	 *
 	 * This is an abstract class, so this constructor should not be used directly.
 	 *
-	 * @param {Iterable} attrs Iterable collection of {@link treeModel.Attribute attributes}.
+	 * @param {Iterable} attrs Iterable collection of attributes.
 	 * @constructor
 	 */
 	constructor( attrs ) {
@@ -40,9 +40,13 @@ export default class Node {
 		 * using {@link treeModel.Node} methods.
 		 *
 		 * @protected
-		 * @property {treeModel.AttributeList} attrs
+		 * @property {Map} _attrs
 		 */
-		this._attrs = new AttributeList( attrs );
+		if ( langUtils.isPlainObject( attrs ) ) {
+			this._attrs = utils.objectToMap( attrs );
+		} else {
+			this._attrs = new Map( attrs );
+		}
 	}
 
 	/**
@@ -163,40 +167,29 @@ export default class Node {
 	}
 
 	/**
-	 * Checks if the node has an attribute that is {@link treeModel.Attribute#isEqual equal} to given attribute or
-	 * attribute with given key if string was passed.
+	 * Checks if the node has an attribute for given key.
 	 *
-	 * @param {treeModel.Attribute|String} attrOrKey Attribute or key of attribute to check.
-	 * @returns {Boolean} `true` if given attribute or attribute with given key is set on node, `false` otherwise.
+	 * @param {String} key Key of attribute to check.
+	 * @returns {Boolean} `true` if attribute with given key is set on node, `false` otherwise.
 	 */
-	hasAttribute( attrOrKey ) {
-		return this._attrs.has( attrOrKey );
+	hasAttribute( key ) {
+		return this._attrs.has( key );
 	}
 
 	/**
-	 * Gets a node's attribute by its key.
+	 * Gets an attribute value for given key or undefined if that attribute is not set on node.
 	 *
 	 * @param {String} key Key of attribute to look for.
-	 * @returns {treeModel.Attribute|null} Attribute with given key or null if the attribute has not been set on node.
+	 * @returns {*} Attribute value or null.
 	 */
 	getAttribute( key ) {
 		return this._attrs.get( key );
 	}
 
 	/**
-	 * Gets a node's attribute value by attribute key.
-	 *
-	 * @param {String} key Key of attribute to look for.
-	 * @returns {*} Value of attribute with given key or null if the attribute has not been set on node.
-	 */
-	getAttributeValue( key ) {
-		return this._attrs.getValue( key );
-	}
-
-	/**
-	 * Returns iterator that iterates over this nodes attributes.
+	 * Returns iterator that iterates over this node attributes.
 	 *
-	 * @returns {Iterable.<treeModel.Attribute>}
+	 * @returns {Iterable.<*>}
 	 */
 	getAttributes() {
 		return this._attrs[ Symbol.iterator ]();

+ 9 - 9
packages/ckeditor5-engine/src/treemodel/nodelist.js

@@ -95,7 +95,7 @@ export default class NodeList {
 	 *		let nodeList = new NodeList( 'foo' );
 	 *		nodeList.length; // 3
 	 *
-	 *		let nodeList = new NodeList( new Text( 'foo', [ new Attribute( 'bar', 'bom' ) ] ) );
+	 *		let nodeList = new NodeList( new Text( 'foo', { bar: 'bom' } ) );
 	 *		nodeList.length; // 3
 	 *		nodeList.get( 0 ).getAttribute( 'bar' ); // 'bom'
 	 *		nodeList.get( 1 ).getAttribute( 'bar' ); // 'bom'
@@ -158,7 +158,7 @@ export default class NodeList {
 
 					let prev = this._nodes[ this._nodes.length - 1 ];
 
-					if ( prev instanceof NodeListText && prev._attrs.isEqual( node._attrs ) ) {
+					if ( prev instanceof NodeListText && utils.mapsEqual( prev._attrs, node._attrs ) ) {
 						// If previously added text has same attributes, merge this text with it.
 						prev.text += node.text;
 						mergedWithPrev = true;
@@ -316,9 +316,9 @@ export default class NodeList {
 	 * @param {Number} index Position of the first node to change.
 	 * @param {Number} number Number of nodes to change.
 	 * @param {String} key Attribute key to change.
-	 * @param {treeModel.Attribute} [attribute] New attribute or null if attribute with given key should be removed.
+	 * @param {*} [attribute] Attribute value or null if attribute with given key should be removed.
 	 */
-	setAttribute( index, number, key, attribute ) {
+	setAttribute( index, number, key, value ) {
 		if ( index < 0 || index + number > this.length ) {
 			/**
 			 * Trying to set attribute on non-existing node list items.
@@ -339,8 +339,8 @@ export default class NodeList {
 			let node = this._nodes[ this._indexMap[ i ] ];
 
 			if ( node instanceof NodeListText ) {
-				if ( attribute ) {
-					node._attrs.set( attribute );
+				if ( value !== null ) {
+					node._attrs.set( key, value );
 				} else {
 					node._attrs.delete( key );
 				}
@@ -348,8 +348,8 @@ export default class NodeList {
 				this._mergeNodeAt( i );
 				i += node.text.length;
 			} else {
-				if ( attribute ) {
-					node.setAttribute( attribute );
+				if ( value !== null ) {
+					node.setAttribute( key, value );
 				} else {
 					node.removeAttribute( key );
 				}
@@ -426,7 +426,7 @@ export default class NodeList {
 		let nodeAfter = this._nodes[ realIndexAfter ];
 
 		// Check if both of those nodes are text objects with same attributes.
-		if ( nodeBefore instanceof NodeListText && nodeAfter instanceof NodeListText && nodeBefore._attrs.isEqual( nodeAfter._attrs ) ) {
+		if ( nodeBefore instanceof NodeListText && nodeAfter instanceof NodeListText && utils.mapsEqual( nodeBefore._attrs, nodeAfter._attrs ) ) {
 			// Append text of text node after index to the before one.
 			nodeBefore.text += nodeAfter.text;
 

+ 32 - 40
packages/ckeditor5-engine/src/treemodel/operation/attributeoperation.js

@@ -29,12 +29,13 @@ export default class AttributeOperation extends Operation {
 	 * old attributes have to have the same key and the old attribute must be present in all nodes in ranges.
 	 *
 	 * @param {treeModel.Range} range Range on which the operation should be applied.
-	 * @param {treeModel.Attribute|null} oldAttr Attribute to be removed. If `null`, then the operation just inserts a new attribute.
-	 * @param {treeModel.Attribute|null} newAttr Attribute to be added. If `null`, then the operation just removes the attribute.
+	 * @param {String} key Key of an attribute to change or remove.
+	 * @param {*} oldValue Old value of the attribute with given key or `null` if adding a new attribute.
+	 * @param {*} newValue New value to set for the attribute. If `null`, then the operation just removes the attribute.
 	 * @param {Number} baseVersion {@link treeModel.Document#version} on which the operation can be applied.
 	 * @constructor
 	 */
-	constructor( range, oldAttr, newAttr, baseVersion ) {
+	constructor( range, key, oldValue, newValue, baseVersion ) {
 		super( baseVersion );
 
 		/**
@@ -46,20 +47,28 @@ export default class AttributeOperation extends Operation {
 		this.range = Range.createFromRange( range );
 
 		/**
-		 * Old attribute to change. Set to `null` if operation inserts a new attribute.
+		 * Key of an attribute to change or remove.
 		 *
 		 * @readonly
-		 * @type {treeModel.Attribute|null}
+		 * @property {String} key
 		 */
-		this.oldAttr = oldAttr;
+		this.key = key;
 
 		/**
-		 * New attribute. Set to `null` if operation removes the attribute.
+		 * Old value of the attribute with given key or `null` if adding a new attribute.
 		 *
 		 * @readonly
-		 * @type {treeModel.Attribute|null}
+		 * @property {*} oldValue
 		 */
-		this.newAttr = newAttr;
+		this.oldValue = oldValue;
+
+		/**
+		 * New value to set for the attribute. If `null`, then the operation just removes the attribute.
+		 *
+		 * @readonly
+		 * @property {*} newValue
+		 */
+		this.newValue = newValue;
 	}
 
 	get type() {
@@ -67,73 +76,56 @@ export default class AttributeOperation extends Operation {
 	}
 
 	clone() {
-		return new AttributeOperation( this.range, this.oldAttr, this.newAttr, this.baseVersion );
+		return new AttributeOperation( this.range, this.key, this.oldValue, this.newValue, this.baseVersion );
 	}
 
 	getReversed() {
-		return new AttributeOperation( this.range, this.newAttr, this.oldAttr, this.baseVersion + 1 );
+		return new AttributeOperation( this.range, this.key, this.newValue, this.oldValue, this.baseVersion + 1 );
 	}
 
 	_execute() {
-		const oldAttr = this.oldAttr;
-		const newAttr = this.newAttr;
-
-		if ( oldAttr !== null && newAttr !== null && oldAttr.key != newAttr.key ) {
-			/**
-			 * Old and new attributes should have the same keys.
-			 *
-			 * @error operation-attribute-different-keys
-			 * @param {treeModel.Attribute} oldAttr
-			 * @param {treeModel.Attribute} newAttr
-			 */
-			throw new CKEditorError(
-				'operation-attribute-different-keys: Old and new attributes should have the same keys.',
-				{ oldAttr: oldAttr, newAttr: newAttr }
-			);
-		}
-
 		for ( let item of this.range.getAllNodes( true ) ) {
-			if ( oldAttr !== null && !item.hasAttribute( oldAttr ) ) {
+			if ( this.oldValue !== null && item.getAttribute( this.key ) !== this.oldValue ) {
 				/**
 				 * The attribute which should be removed does not exists for the given node.
 				 *
 				 * @error operation-attribute-no-attr-to-remove
 				 * @param {treeModel.Node} node
-				 * @param {treeModel.Attribute} attr
+				 * @param {String} key
+				 * @param {*} value
 				 */
 				throw new CKEditorError(
 					'operation-attribute-no-attr-to-remove: The attribute which should be removed does not exists for given node.',
-					{ node: item, attr: oldAttr }
+					{ node: item, key: this.key, value: this.oldValue }
 				);
 			}
 
-			if ( oldAttr === null && newAttr !== null && item.hasAttribute( newAttr.key ) ) {
+			if ( this.oldValue === null && this.newValue !== null && item.hasAttribute( this.key ) ) {
 				/**
 				 * The attribute with given key already exists for the given node.
 				 *
 				 * @error operation-attribute-attr-exists
 				 * @param {treeModel.Node} node
-				 * @param {treeModel.Attribute} attr
+				 * @param {String} key
 				 */
 				throw new CKEditorError(
 					'operation-attribute-attr-exists: The attribute with given key already exists.',
-					{ node: item, attr: newAttr }
+					{ node: item, key: this.key }
 				);
 			}
 
 			if ( item instanceof TextFragment ) {
-				let key = newAttr ? newAttr.key : oldAttr.key;
-				item.commonParent._children.setAttribute( item.first.getIndex(), item.text.length, key, newAttr );
+				item.commonParent._children.setAttribute( item.first.getIndex(), item.text.length, this.key, this.newValue );
 			}
 			else {
-				if ( newAttr ) {
-					item.setAttribute( newAttr );
+				if ( this.newValue !== null ) {
+					item.setAttribute( this.key, this.newValue );
 				} else {
-					item.removeAttribute( oldAttr.key );
+					item.removeAttribute( this.key );
 				}
 			}
 		}
 
-		return { range: this.range, oldAttr: oldAttr, newAttr: newAttr };
+		return { range: this.range, key: this.key, oldValue: this.oldValue, newValue: this.newValue };
 	}
 }

+ 8 - 43
packages/ckeditor5-engine/src/treemodel/operation/transform.js

@@ -11,6 +11,7 @@ import MoveOperation from './moveoperation.js';
 import NoOperation from './nooperation.js';
 import Position from '../position.js';
 import Range from '../range.js';
+import langUtils from '../../lib/lodash/lang.js';
 import utils from '../../utils.js';
 
 /**
@@ -89,37 +90,32 @@ const ot = {
 
 			// Map transformed range(s) to operations and return them.
 			return ranges.reverse().map( ( range ) => {
-				return new AttributeOperation(
-					range,
-					a.oldAttr,
-					a.newAttr,
-					a.baseVersion
-				);
+				return new AttributeOperation( range, a.key, a.oldValue, a.newValue, a.baseVersion );
 			} );
 		},
 
 		// Transforms AttributeOperation `a` by AttributeOperation `b`. Accepts a flag stating whether `a` is more important
 		// than `b` when it comes to resolving conflicts. Returns results as an array of operations.
 		AttributeOperation( a, b, isStrong ) {
-			if ( haveConflictingAttributes( a, b ) ) {
+			if ( a.key === b.key ) {
 				// If operations attributes are in conflict, check if their ranges intersect and manage them properly.
 				let operations = [];
 
 				// First, we want to apply change to the part of a range that has not been changed by the other operation.
 				operations = operations.concat(
 					a.range.getDifference( b.range ).map( ( range ) => {
-						return new AttributeOperation( range, a.oldAttr, a.newAttr, a.baseVersion );
+						return new AttributeOperation( range, a.key, a.oldValue, a.newValue, a.baseVersion );
 					} )
 				);
 
-				if ( isStrong ) {
+				if ( isStrong && !langUtils.isEqual( a.newValue, b.newValue ) ) {
 					// If this operation is more important, we want also want to apply change to the part of the
 					// original range that has already been changed by the other operation. Since that range
 					// got changed we have to update oldAttr.
 					const common = a.range.getIntersection( b.range );
 
 					if ( common !== null ) {
-						operations.push( new AttributeOperation( common, b.oldAttr, a.newAttr, a.baseVersion ) );
+						operations.push( new AttributeOperation( common, b.key, b.oldValue, a.newValue, a.baseVersion ) );
 					}
 				}
 
@@ -131,7 +127,7 @@ const ot = {
 
 				return operations;
 			} else {
-				// If operations don't conflict simply, return an array containing just a clone of this operation.
+				// If operations don't conflict, simply return an array containing just a clone of this operation.
 				return [ a.clone() ];
 			}
 		},
@@ -184,12 +180,7 @@ const ot = {
 
 			// Map transformed range(s) to operations and return them.
 			return ranges.map( ( range ) => {
-				return new AttributeOperation(
-					range,
-					a.oldAttr,
-					a.newAttr,
-					a.baseVersion
-				);
+				return new AttributeOperation( range, a.key, a.oldValue, a.newValue, a.baseVersion );
 			} );
 		}
 	},
@@ -381,32 +372,6 @@ function moveTargetIntoMovedRange( a, b ) {
 	return a.targetPosition.getTransformedByDeletion( b.sourcePosition, b.howMany ) === null;
 }
 
-// Takes two AttributeOperations and checks whether their attributes are in conflict.
-// This happens when both operations changes an attribute with the same key and they either set different
-// values for this attribute or one of them removes it while the other one sets it.
-// Returns true if attributes are in conflict.
-function haveConflictingAttributes( a, b ) {
-	// Keeping in mind that newAttr or oldAttr might be null.
-	// We will retrieve the key from whichever parameter is set.
-	const keyA = ( a.newAttr || a.oldAttr ).key;
-	const keyB = ( b.newAttr || b.oldAttr ).key;
-
-	if ( keyA != keyB ) {
-		// Different keys - not conflicting.
-		return false;
-	}
-
-	if ( a.newAttr === null && b.newAttr === null ) {
-		// Both remove the attribute - not conflicting.
-		return false;
-	}
-
-	// Check if they set different value or one of them removes the attribute.
-	return ( a.newAttr === null && b.newAttr !== null ) ||
-		( a.newAttr !== null && b.newAttr === null ) ||
-		( !a.newAttr.isEqual( b.newAttr ) );
-}
-
 // Gets an array of Ranges and produces one Range out of it. The root of a new range will be same as
 // the root of the first range in the array. If any of given ranges has different root than the first range,
 // it will be discarded.

+ 24 - 34
packages/ckeditor5-engine/src/treemodel/selection.js

@@ -6,9 +6,9 @@
 'use strict';
 
 import LiveRange from './liverange.js';
-import AttributeList from './attributelist.js';
 import EmitterMixin from '../emittermixin.js';
 import CKEditorError from '../ckeditorerror.js';
+import langUtils from '../lib/lodash/lang.js';
 import utils from '../utils.js';
 
 /**
@@ -28,9 +28,9 @@ export default class Selection {
 		 * List of attributes set on current selection.
 		 *
 		 * @protected
-		 * @property {treeModel.AttributeList}
+		 * @property {Map}
 		 */
-		this._attrs = new AttributeList();
+		this._attrs = new Map();
 
 		/**
 		 * Stores all ranges that are selected.
@@ -169,40 +169,29 @@ export default class Selection {
 	}
 
 	/**
-	 * Checks if the selection has an attribute that is {@link treeModel.Attribute#isEqual equal} to given attribute or
-	 * attribute with given key if string was passed.
+	 * Checks if the selection has an attribute for given key.
 	 *
-	 * @param {treeModel.Attribute|String} attrOrKey Attribute or key of attribute to check.
-	 * @returns {Boolean} `true` if given attribute or attribute with given key is set on selection, `false` otherwise.
+	 * @param {String} key Key of attribute to check.
+	 * @returns {Boolean} `true` if attribute with given key is set on selection, `false` otherwise.
 	 */
-	hasAttribute( attrOrKey ) {
-		return this._attrs.has( attrOrKey );
+	hasAttribute( key ) {
+		return this._attrs.has( key );
 	}
 
 	/**
-	 * Gets a selection's attribute by its key.
+	 * Gets an attribute value for given key or undefined it that attribute is not set on selection.
 	 *
 	 * @param {String} key Key of attribute to look for.
-	 * @returns {treeModel.Attribute|null} Attribute with given key or null if the attribute has not been set on selection.
+	 * @returns {*} Attribute value or null.
 	 */
 	getAttribute( key ) {
 		return this._attrs.get( key );
 	}
 
 	/**
-	 * Gets a selection's attribute value by attribute key.
+	 * Returns iterator that iterates over this selection attributes.
 	 *
-	 * @param {String} key Key of attribute to look for.
-	 * @returns {*} Value of attribute with given key or null if the attribute has not been set on selection.
-	 */
-	getAttributeValue( key ) {
-		return this._attrs.getValue( key );
-	}
-
-	/**
-	 * Returns iterator that iterates over this nodes attributes.
-	 *
-	 * @returns {Iterable.<treeModel.Attribute>}
+	 * @returns {Iterable.<*>}
 	 */
 	getAttributes() {
 		return this._attrs[ Symbol.iterator ]();
@@ -211,37 +200,38 @@ export default class Selection {
 	/**
 	 * Sets attribute on the selection. If attribute with the same key already is set, it overwrites its values.
 	 *
-	 * @chainable
-	 * @param {treeModel.Attribute} attr Attribute to set or overwrite with.
-	 * @returns {treeModel.Selection} This selection.
+	 * @param {String} key Key of attribute to set.
+	 * @param {*} value Attribute value.
 	 */
-	setAttribute( attr ) {
-		this._attrs.set( attr );
-
-		return this;
+	setAttribute( key, value ) {
+		this._attrs.set( key, value );
 	}
 
 	/**
 	 * Removes all attributes from the selection and sets given attributes.
 	 *
-	 * @param {Iterable.<treeModel.Attribute>} attrs Iterable object containing {@link treeModel.Attribute attributes} to be set.
+	 * @param {Iterable.<*>} attrs Iterable object containing attributes to be set.
 	 */
 	setAttributesTo( attrs ) {
-		this._attrs.setTo( attrs );
+		if ( langUtils.isPlainObject( attrs ) ) {
+			this._attrs = utils.objectToMap( attrs );
+		} else {
+			this._attrs = new Map( attrs );
+		}
 	}
 
 	/**
 	 * Removes an attribute with given key from the selection.
 	 *
 	 * @param {String} key Key of attribute to remove.
-	 * @returns {Boolean} `true` if the attribute was set on the element, `false` otherwise.
+	 * @returns {Boolean} `true` if the attribute was set on the selection, `false` otherwise.
 	 */
 	removeAttribute( key ) {
 		return this._attrs.delete( key );
 	}
 
 	/**
-	 * Removes all attributes from the element.
+	 * Removes all attributes from the selection.
 	 */
 	clearAttributes() {
 		this._attrs.clear();

+ 11 - 8
packages/ckeditor5-engine/src/treemodel/text.js

@@ -5,15 +5,14 @@
 
 'use strict';
 
-import AttributeList from './attributelist.js';
+import langUtils from '../lib/lodash/lang.js';
+import utils from '../utils.js';
 
 /**
  * Data structure for text with attributes. Note that `Text` is not a {@link treeModel.Node}. This class is used
  * as an aggregator for multiple characters that have same attributes. Example usage:
  *
- *		let attrFoo = new Attribute( 'foo', true );
- *		let attrBar = new Attribute( 'bar', true );
- *		let myElem = new Element( 'li', [], new Text( 'text with attributes', [ attrFoo, attrBar ] ) );
+ *		let myElem = new Element( 'li', [], new Text( 'text with attributes', { foo: true, bar: true } ) );
  *
  * @class treeModel.Text
  */
@@ -22,7 +21,7 @@ export default class Text {
 	 * Creates a text with attributes.
 	 *
 	 * @param {String} text Described text.
-	 * @param {Iterable} attrs Iterable collection of {@link treeModel.Attribute attributes}.
+	 * @param {Iterable} attrs Iterable collection of attributes.
 	 * @constructor
 	 */
 	constructor( text, attrs ) {
@@ -35,11 +34,15 @@ export default class Text {
 		this.text = text || '';
 
 		/**
-		 * {@link treeModel.Attribute AttributesList} bound with the text.
+		 * List of attributes bound with the text.
 		 *
 		 * @protected
-		 * @property {treeModel.AttributeList}
+		 * @property {Map}
 		 */
-		this._attrs = new AttributeList( attrs );
+		if ( langUtils.isPlainObject( attrs ) ) {
+			this._attrs = utils.objectToMap( attrs );
+		} else {
+			this._attrs = new Map( attrs );
+		}
 	}
 }

+ 10 - 62
packages/ckeditor5-engine/src/treemodel/textfragment.js

@@ -24,7 +24,7 @@ export default class TextFragment {
 	 * Creates a text fragment.
 	 *
 	 * @param {treeModel.CharacterProxy} firstCharacter First character node contained in {@link treeModel.TextFragment}.
-	 * @param {Number) length Whole text contained in {@link treeModel.TextFragment}.
+	 * @param {Number} length Whole text contained in {@link treeModel.TextFragment}.
 	 * @protected
 	 * @constructor
 	 */
@@ -78,83 +78,31 @@ export default class TextFragment {
 	}
 
 	/**
-	 * Checks if the text fragment has an attribute that is {@link treeModel.Attribute#isEqual equal} to given attribute or
-	 * attribute with given key if string was passed.
+	 * Checks if the text fragment has an attribute for given key.
 	 *
-	 * @param {treeModel.Attribute|String} attrOrKey Attribute or key of attribute to check.
-	 * @returns {Boolean} `true` if given attribute or attribute with given key is set on text fragment, `false` otherwise.
+	 * @param {String} key Key of attribute to check.
+	 * @returns {Boolean} `true` if attribute with given key is set on text fragment, `false` otherwise.
 	 */
-	hasAttribute( attrOrKey ) {
-		return this.first.hasAttribute( attrOrKey );
+	hasAttribute( key ) {
+		return this.first.hasAttribute( key );
 	}
 
 	/**
-	 * Gets a text fragment attribute by its key.
+	 * Gets an attribute value for given key or undefined it that attribute is not set on text fragment.
 	 *
 	 * @param {String} key Key of attribute to look for.
-	 * @returns {treeModel.Attribute|null} Attribute with given key or null if the attribute has not been set on the text fragment.
+	 * @returns {*} Attribute value or null.
 	 */
 	getAttribute( key ) {
 		return this.first.getAttribute( key );
 	}
 
 	/**
-	 * Gets a text fragment attribute value by attribute key.
+	 * Returns iterator that iterates over this text fragment attributes.
 	 *
-	 * @param {String} key Key of attribute to look for.
-	 * @returns {*} Value of attribute with given key or null if the attribute has not been set on the text fragment.
-	 */
-	getAttributeValue( key ) {
-		return this.first.getAttributeValue( key );
-	}
-
-	/**
-	 * Returns iterator that iterates over this text fragment's attributes.
-	 *
-	 * @returns {Iterable.<treeModel.Attribute>}
+	 * @returns {Iterable.<*>}
 	 */
 	getAttributes() {
 		return this.first.getAttributes();
 	}
-
-	/**
-	 * Sets attribute on the text fragment. If attribute with the same key already is set, it overwrites its values.
-	 *
-	 * To change attributes of nodes (also characters) that are attached to the tree model, you
-	 * should use {@link treeModel.AttributeDelta}. This method is used by tree model internal mechanisms.
-	 *
-	 * @protected
-	 * @param {treeModel.Attribute} attr Attribute to set or overwrite with.
-	 */
-	setAttribute( attr ) {
-		// Do note that this changes attributes on whole NodeListText, not only on character nodes specified by
-		// this TextFragment. Split NodeList at proper index before using this.
-		this.first._nodeListText._attrs.set( attr );
-
-		// Refreshing first and last character proxies because they would have wrong attributes.
-		this.first = this.getCharAt( 0 );
-		this.last = this.getCharAt( this.text.length - 1 );
-	}
-
-	/**
-	 * Removes an attribute with given key from the text fragment.
-	 *
-	 * To change attributes of nodes (also characters) that are attached to the tree model, you
-	 * should use {@link treeModel.AttributeDelta}. This method is used by tree model internal mechanisms.
-	 *
-	 * @protected
-	 * @param {String} key Key of attribute to remove.
-	 * @returns {Boolean} `true` if the attribute was set on the element, `false` otherwise.
-	 */
-	removeAttribute( key ) {
-		// Do note that this changes attributes on whole NodeListText, not only on character nodes specified by
-		// this TextFragment. Split NodeList at proper index before using this.
-		let result = this.first._nodeListText._attrs.delete( key );
-
-		// Refreshing first and last character proxies because they would have wrong attributes.
-		this.first = this.getCharAt( 0 );
-		this.last = this.getCharAt( this.text.length - 1 );
-
-		return result;
-	}
 }

+ 0 - 28
packages/ckeditor5-engine/tests/treemodel/attribute.js

@@ -1,28 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-/* bender-tags: treemodel */
-
-'use strict';
-
-import Attribute from '/ckeditor5/core/treemodel/attribute.js';
-
-describe( 'Attribute', () => {
-	describe( 'constructor', () => {
-		it( 'should create attribute', () => {
-			let attr = new Attribute( 'foo', 'bar' );
-
-			expect( attr ).to.have.property( 'key' ).that.equals( 'foo' );
-			expect( attr ).to.have.property( 'value' ).that.equals( 'bar' );
-		} );
-
-		it( 'should create equal instance even if object has different order', () => {
-			let attr1 = new Attribute( 'foo', { a: 1, b: 2 } );
-			let attr2 = new Attribute( 'foo', { b: 2, a: 1 } );
-
-			expect( attr1.isEqual( attr2 ) ).to.be.true;
-		} );
-	} );
-} );

+ 0 - 190
packages/ckeditor5-engine/tests/treemodel/attributelist.js

@@ -1,190 +0,0 @@
-/**
- * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
-
-/* bender-tags: treemodel */
-
-'use strict';
-
-import AttributeList from '/ckeditor5/core/treemodel/attributelist.js';
-import Attribute from '/ckeditor5/core/treemodel/attribute.js';
-
-describe( 'AttributeList', () => {
-	let list, attrFooBar;
-
-	beforeEach( () => {
-		list = new AttributeList();
-		attrFooBar = new Attribute( 'foo', 'bar' );
-	} );
-
-	it( 'should extend Map', () => {
-		expect( list ).to.be.instanceof( Map );
-	} );
-
-	describe( 'constructor', () => {
-		it( 'should initialize list with passed attributes', () => {
-			list = new AttributeList( [ attrFooBar ] );
-			expect( list.size ).to.equal( 1 );
-			expect( list.has( 'foo' ) ).to.be.true;
-			expect( list.get( 'foo' ).value ).to.equal( 'bar' );
-		} );
-
-		it( 'should copy passed AttributeList', () => {
-			list = new AttributeList( [ attrFooBar ] );
-			let copy = new AttributeList( list );
-
-			expect( copy.size ).to.equal( 1 );
-			expect( copy.has( 'foo' ) ).to.be.true;
-			expect( copy.get( 'foo' ).value ).to.equal( 'bar' );
-		} );
-	} );
-
-	describe( 'iterator', () => {
-		it( 'should iterate over all added attributes', () => {
-			let attrAbcXyz = new Attribute( 'abc', 'xyz' );
-			let attrTestTrue = new Attribute( 'test', true );
-
-			list = new AttributeList( [ attrFooBar, attrAbcXyz, attrTestTrue ] );
-			list.delete( 'test' );
-
-			let it = list[ Symbol.iterator ]();
-
-			let step = it.next();
-
-			expect( step.done ).to.be.false;
-			expect( step.value ).to.equal( attrFooBar );
-
-			step = it.next();
-
-			expect( step.done ).to.be.false;
-			expect( step.value ).to.equal( attrAbcXyz );
-
-			step = it.next();
-
-			expect( step.done ).to.be.true;
-		} );
-	} );
-
-	describe( 'getValue', () => {
-		it( 'should return value of set attribute for given key', () => {
-			list.set( attrFooBar );
-			expect( list.getValue( 'foo' ) ).to.equal( 'bar' );
-		} );
-
-		it( 'should return null if attribute with given key is not set', () => {
-			expect( list.getValue( 'foo' ) ).to.be.null;
-		} );
-	} );
-
-	describe( 'set', () => {
-		it( 'should insert given attribute', () => {
-			list.set( attrFooBar );
-
-			expect( list.size ).to.equal( 1 );
-			expect( list.getValue( 'foo' ) ).to.equal( 'bar' );
-		} );
-
-		it( 'should overwrite attribute with the same key', () => {
-			list.set( attrFooBar );
-
-			expect( list.size ).to.equal( 1 );
-			expect( list.getValue( 'foo' ) ).to.equal( 'bar' );
-
-			let attrFooXyz = new Attribute( 'foo', 'xyz' );
-
-			list.set( attrFooXyz );
-
-			expect( list.size ).to.equal( 1 );
-			expect( list.getValue( 'foo' ) ).to.equal( 'xyz' );
-		} );
-	} );
-
-	describe( 'setTo', () => {
-		it( 'should remove all attributes from the list and set given ones', () => {
-			list.set( attrFooBar );
-			list.setTo( [ new Attribute( 'abc', true ), new Attribute( 'bar', false ) ] );
-
-			expect( list.has( 'foo' ) ).to.be.false;
-			expect( list.getValue( 'abc' ) ).to.be.true;
-			expect( list.getValue( 'bar' ) ).to.be.false;
-		} );
-	} );
-
-	describe( 'has', () => {
-		it( 'should return true if list contains given attribute (same key and value)', () => {
-			list.set( attrFooBar );
-
-			expect( list.has( attrFooBar ) ).to.be.true;
-		} );
-
-		it( 'should return true if list contains an attribute with given key', () => {
-			list.set( attrFooBar );
-
-			expect( list.has( 'foo' ) ).to.be.true;
-		} );
-
-		it( 'should return false if list does not contain given attribute', () => {
-			list.set( attrFooBar );
-
-			expect( list.has( new Attribute( 'abc', true ) ) ).to.be.false;
-		} );
-
-		it( 'should return false if list contains given attribute but value differs', () => {
-			list.set( attrFooBar );
-
-			expect( list.has( new Attribute( 'foo', 'foo' ) ) ).to.be.false;
-		} );
-
-		it( 'should return false if list does not contain an attribute with given key', () => {
-			list.set( attrFooBar );
-
-			expect( list.has( 'abc' ) ).to.be.false;
-		} );
-	} );
-
-	describe( 'isEqual', () => {
-		it( 'should return false if lists have different size', () => {
-			let attrAbcXyz = new Attribute( 'abc', 'xyz' );
-			list.setTo( [ attrFooBar, attrAbcXyz ] );
-
-			let other = new AttributeList( [ attrFooBar ] );
-
-			expect( list.isEqual( other ) ).to.be.false;
-			expect( other.isEqual( list ) ).to.be.false;
-		} );
-
-		it( 'should return false if lists have different attributes', () => {
-			let attrAbcXyz = new Attribute( 'abc', 'xyz' );
-			list.setTo( [ attrFooBar ] );
-
-			let other = new AttributeList( [ attrAbcXyz ] );
-
-			expect( list.isEqual( other ) ).to.be.false;
-			expect( other.isEqual( list ) ).to.be.false;
-		} );
-
-		it( 'should return false if lists have same attributes but different values for them', () => {
-			let attrAbcXyz = new Attribute( 'abc', 'xyz' );
-			let attrFooTrue = new Attribute( 'foo', true );
-
-			list.setTo( [ attrFooBar, attrAbcXyz ] );
-
-			let other = new AttributeList( [ attrFooTrue, attrAbcXyz ] );
-
-			expect( list.isEqual( other ) ).to.be.false;
-			expect( other.isEqual( list ) ).to.be.false;
-		} );
-
-		it( 'should return true if lists have same attributes and same values for them', () => {
-			let attrAbcXyz = new Attribute( 'abc', 'xyz' );
-			list.setTo( [ attrFooBar, attrAbcXyz ] );
-
-			// Note different order of attributes.
-			let other = new AttributeList( [ attrAbcXyz, attrFooBar ] );
-
-			expect( list.isEqual( other ) ).to.be.true;
-			expect( other.isEqual( list ) ).to.be.true;
-		} );
-	} );
-} );

+ 3 - 3
packages/ckeditor5-engine/tests/treemodel/characterproxy.js

@@ -10,13 +10,13 @@
 import Node from '/ckeditor5/core/treemodel/node.js';
 import Element from '/ckeditor5/core/treemodel/element.js';
 import Text from '/ckeditor5/core/treemodel/text.js';
-import Attribute from '/ckeditor5/core/treemodel/attribute.js';
+import utils from '/ckeditor5/core/utils.js';
 
 describe( 'CharacterProxy', () => {
 	let text, element, char;
 
 	before( () => {
-		text = new Text( 'abc', [ new Attribute( 'foo', true ) ] );
+		text = new Text( 'abc', { foo: true } );
 		element = new Element( 'div', [], [ new Element( 'p' ), text, new Element( 'p' ) ] );
 	} );
 
@@ -37,7 +37,7 @@ describe( 'CharacterProxy', () => {
 	} );
 
 	it( 'should have attributes list equal to passed to Text instance', () => {
-		expect( char._attrs.isEqual( text._attrs ) ).to.be.true;
+		expect( utils.mapsEqual( char._attrs, text._attrs ) ).to.be.true;
 	} );
 
 	it( 'should return correct index in parent node', () => {

+ 15 - 16
packages/ckeditor5-engine/tests/treemodel/delta/attributedelta.js

@@ -10,7 +10,6 @@
 import coreTestUtils from '/tests/core/_utils/utils.js';
 import Document from '/ckeditor5/core/treemodel/document.js';
 import Text from '/ckeditor5/core/treemodel/text.js';
-import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 import Range from '/ckeditor5/core/treemodel/range.js';
 import Position from '/ckeditor5/core/treemodel/position.js';
 import Element from '/ckeditor5/core/treemodel/element.js';
@@ -40,8 +39,8 @@ describe( 'Batch', () => {
 		let node, text, char;
 
 		beforeEach( () => {
-			node = new Element( 'p', [ new Attribute( 'a', 1 ) ] );
-			text = new Text( 'c', [ new Attribute( 'a', 1 ) ] );
+			node = new Element( 'p', { a: 1 } );
+			text = new Text( 'c', { a: 1 } );
 
 			root.insertChildren( 0, [ node, text ] );
 
@@ -52,31 +51,31 @@ describe( 'Batch', () => {
 			it( 'should create the attribute on element', () => {
 				batch.setAttr( 'b', 2, node );
 				expect( getOperationsCount() ).to.equal( 1 );
-				expect( node.getAttributeValue( 'b' ) ).to.equal( 2 );
+				expect( node.getAttribute( 'b' ) ).to.equal( 2 );
 			} );
 
 			it( 'should change the attribute of element', () => {
 				batch.setAttr( 'a', 2, node );
 				expect( getOperationsCount() ).to.equal( 1 );
-				expect( node.getAttributeValue( 'a' ) ).to.equal( 2 );
+				expect( node.getAttribute( 'a' ) ).to.equal( 2 );
 			} );
 
 			it( 'should create the attribute on text node', () => {
 				batch.setAttr( 'b', 2, char );
 				expect( getOperationsCount() ).to.equal( 1 );
-				expect( root.getChild( 1 ).getAttributeValue( 'b' ) ).to.equal( 2 );
+				expect( root.getChild( 1 ).getAttribute( 'b' ) ).to.equal( 2 );
 			} );
 
 			it( 'should change the attribute of text node', () => {
 				batch.setAttr( 'a', 2, char );
 				expect( getOperationsCount() ).to.equal( 1 );
-				expect( root.getChild( 1 ).getAttributeValue( 'a' ) ).to.equal( 2 );
+				expect( root.getChild( 1 ).getAttribute( 'a' ) ).to.equal( 2 );
 			} );
 
 			it( 'should do nothing if the attribute value is the same', () => {
 				batch.setAttr( 'a', 1, node );
 				expect( getOperationsCount() ).to.equal( 0 );
-				expect( node.getAttributeValue( 'a' ) ).to.equal( 1 );
+				expect( node.getAttribute( 'a' ) ).to.equal( 1 );
 			} );
 
 			it( 'should be chainable', () => {
@@ -89,13 +88,13 @@ describe( 'Batch', () => {
 			it( 'should remove the attribute from element', () => {
 				batch.removeAttr( 'a', node );
 				expect( getOperationsCount() ).to.equal( 1 );
-				expect( node.getAttributeValue( 'a' ) ).to.be.null;
+				expect( node.getAttribute( 'a' ) ).to.be.undefined;
 			} );
 
 			it( 'should remove the attribute from character', () => {
 				batch.removeAttr( 'a', char );
 				expect( getOperationsCount() ).to.equal( 1 );
-				expect( root.getChild( 1 ).getAttributeValue( 'a' ) ).to.be.null;
+				expect( root.getChild( 1 ).getAttribute( 'a' ) ).to.be.undefined;
 			} );
 
 			it( 'should do nothing if the attribute is not set', () => {
@@ -113,13 +112,13 @@ describe( 'Batch', () => {
 	describe( 'change attribute on range', () => {
 		beforeEach( () => {
 			root.insertChildren( 0, [
-				new Text( 'xxx', [ new Attribute( 'a', 1 ) ] ),
+				new Text( 'xxx', { a: 1 } ),
 				'xxx',
-				new Text( 'xxx', [ new Attribute( 'a', 1 ) ] ),
-				new Text( 'xxx', [ new Attribute( 'a', 2 ) ] ),
+				new Text( 'xxx', { a: 1 } ),
+				new Text( 'xxx', { a: 2 } ),
 				'xxx',
-				new Text( 'xxx', [ new Attribute( 'a', 1 ) ] ),
-				new Element( 'e', [ new Attribute( 'a', 2 ) ], 'xxx' ),
+				new Text( 'xxx', { a: 1 } ),
+				new Element( 'e', { a: 2 }, 'xxx' ),
 				'xxx'
 			] );
 		} );
@@ -147,7 +146,7 @@ describe( 'Batch', () => {
 			// default: 111---111222---1112------
 			const range = Range.createFromElement( root );
 
-			return Array.from( range.getAllNodes() ).map( node => node.getAttributeValue( 'a' ) || '-' ).join( '' );
+			return Array.from( range.getAllNodes() ).map( node => node.getAttribute( 'a' ) || '-' ).join( '' );
 		}
 
 		describe( 'setAttr', () => {

+ 3 - 4
packages/ckeditor5-engine/tests/treemodel/delta/mergedelta.js

@@ -10,7 +10,6 @@
 import Document from '/ckeditor5/core/treemodel/document.js';
 import Position from '/ckeditor5/core/treemodel/position.js';
 import Element from '/ckeditor5/core/treemodel/element.js';
-import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
 describe( 'Batch', () => {
@@ -20,8 +19,8 @@ describe( 'Batch', () => {
 		doc = new Document();
 		root = doc.createRoot( 'root' );
 
-		p1 = new Element( 'p', [ new Attribute( 'key1', 'value1' ) ], 'foo' );
-		p2 = new Element( 'p', [ new Attribute( 'key2', 'value2' ) ], 'bar' );
+		p1 = new Element( 'p', { key1: 'value1' }, 'foo' );
+		p2 = new Element( 'p', { key2: 'value2' }, 'bar' );
 
 		root.insertChildren( 0, [ p1, p2 ] );
 	} );
@@ -34,7 +33,7 @@ describe( 'Batch', () => {
 			expect( root.getChild( 0 ).name ).to.equal( 'p' );
 			expect( root.getChild( 0 ).getChildCount() ).to.equal( 6 );
 			expect( root.getChild( 0 )._attrs.size ).to.equal( 1 );
-			expect( root.getChild( 0 ).getAttributeValue( 'key1' ) ).to.equal( 'value1' );
+			expect( root.getChild( 0 ).getAttribute( 'key1' ) ).to.equal( 'value1' );
 			expect( root.getChild( 0 ).getChild( 0 ).character ).to.equal( 'f' );
 			expect( root.getChild( 0 ).getChild( 1 ).character ).to.equal( 'o' );
 			expect( root.getChild( 0 ).getChild( 2 ).character ).to.equal( 'o' );

+ 5 - 6
packages/ckeditor5-engine/tests/treemodel/delta/splitdelta.js

@@ -10,7 +10,6 @@
 import Document from '/ckeditor5/core/treemodel/document.js';
 import Position from '/ckeditor5/core/treemodel/position.js';
 import Element from '/ckeditor5/core/treemodel/element.js';
-import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
 describe( 'Batch', () => {
@@ -20,7 +19,7 @@ describe( 'Batch', () => {
 		doc = new Document();
 		root = doc.createRoot( 'root' );
 
-		p = new Element( 'p', [ new Attribute( 'key', 'value' ) ], 'foobar' );
+		p = new Element( 'p', { key: 'value' }, 'foobar' );
 
 		root.insertChildren( 0, p );
 	} );
@@ -34,7 +33,7 @@ describe( 'Batch', () => {
 			expect( root.getChild( 0 ).name ).to.equal( 'p' );
 			expect( root.getChild( 0 ).getChildCount() ).to.equal( 3 );
 			expect( root.getChild( 0 )._attrs.size ).to.equal( 1 );
-			expect( root.getChild( 0 ).getAttributeValue( 'key' ) ).to.equal( 'value' );
+			expect( root.getChild( 0 ).getAttribute( 'key' ) ).to.equal( 'value' );
 			expect( root.getChild( 0 ).getChild( 0 ).character ).to.equal( 'f' );
 			expect( root.getChild( 0 ).getChild( 1 ).character ).to.equal( 'o' );
 			expect( root.getChild( 0 ).getChild( 2 ).character ).to.equal( 'o' );
@@ -42,7 +41,7 @@ describe( 'Batch', () => {
 			expect( root.getChild( 1 ).name ).to.equal( 'p' );
 			expect( root.getChild( 1 ).getChildCount() ).to.equal( 3 );
 			expect( root.getChild( 1 )._attrs.size ).to.equal( 1 );
-			expect( root.getChild( 1 ).getAttributeValue( 'key' ) ).to.equal( 'value' );
+			expect( root.getChild( 1 ).getAttribute( 'key' ) ).to.equal( 'value' );
 			expect( root.getChild( 1 ).getChild( 0 ).character ).to.equal( 'b' );
 			expect( root.getChild( 1 ).getChild( 1 ).character ).to.equal( 'a' );
 			expect( root.getChild( 1 ).getChild( 2 ).character ).to.equal( 'r' );
@@ -56,7 +55,7 @@ describe( 'Batch', () => {
 			expect( root.getChild( 0 ).name ).to.equal( 'p' );
 			expect( root.getChild( 0 ).getChildCount() ).to.equal( 6 );
 			expect( root.getChild( 0 )._attrs.size ).to.equal( 1 );
-			expect( root.getChild( 0 ).getAttributeValue( 'key' ) ).to.equal( 'value' );
+			expect( root.getChild( 0 ).getAttribute( 'key' ) ).to.equal( 'value' );
 			expect( root.getChild( 0 ).getChild( 0 ).character ).to.equal( 'f' );
 			expect( root.getChild( 0 ).getChild( 1 ).character ).to.equal( 'o' );
 			expect( root.getChild( 0 ).getChild( 2 ).character ).to.equal( 'o' );
@@ -67,7 +66,7 @@ describe( 'Batch', () => {
 			expect( root.getChild( 1 ).name ).to.equal( 'p' );
 			expect( root.getChild( 1 ).getChildCount() ).to.equal( 0 );
 			expect( root.getChild( 1 )._attrs.size ).to.equal( 1 );
-			expect( root.getChild( 1 ).getAttributeValue( 'key' ) ).to.equal( 'value' );
+			expect( root.getChild( 1 ).getAttribute( 'key' ) ).to.equal( 'value' );
 		} );
 
 		it( 'should throw if we try to split a root', () => {

+ 1 - 5
packages/ckeditor5-engine/tests/treemodel/delta/weakinsertdelta.js

@@ -8,7 +8,6 @@
 'use strict';
 
 import Document from '/ckeditor5/core/treemodel/document.js';
-import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 import Position from '/ckeditor5/core/treemodel/position.js';
 
 describe( 'Batch', () => {
@@ -22,10 +21,7 @@ describe( 'Batch', () => {
 
 		batch = doc.batch();
 
-		attrs = [
-			new Attribute( 'bold', true ),
-			new Attribute( 'foo', 'bar' )
-		];
+		attrs = [ [ 'bold', true ], [ 'foo', 'bar' ] ];
 
 		doc.selection.setAttributesTo( attrs );
 

+ 18 - 13
packages/ckeditor5-engine/tests/treemodel/document/change-event.js

@@ -9,7 +9,6 @@
 
 import Document from '/ckeditor5/core/treemodel/document.js';
 import Element from '/ckeditor5/core/treemodel/element.js';
-import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 import Position from '/ckeditor5/core/treemodel/position.js';
 import Range from '/ckeditor5/core/treemodel/range.js';
 import AttributeOperation from '/ckeditor5/core/treemodel/operation/attributeoperation.js';
@@ -109,8 +108,9 @@ describe( 'Document change event', () => {
 		doc.applyOperation(
 			new AttributeOperation(
 				Range.createFromParentsAndOffsets( root, 0, root, 3 ),
+				'key',
 				null,
-				new Attribute( 'key', 'new' ),
+				'new',
 				doc.version
 			)
 		);
@@ -118,18 +118,20 @@ describe( 'Document change event', () => {
 		expect( changes ).to.have.length( 1 );
 		expect( types[ 0 ] ).to.equal( 'attr' );
 		expect( changes[ 0 ].range ).to.deep.equal( Range.createFromParentsAndOffsets( root, 0, root, 3 ) );
-		expect( changes[ 0 ].oldAttr ).to.be.null;
-		expect( changes[ 0 ].newAttr ).to.deep.equal( new Attribute( 'key', 'new' ) );
+		expect( changes[ 0 ].key ).to.equal( 'key' );
+		expect( changes[ 0 ].oldValue ).to.be.null;
+		expect( changes[ 0 ].newValue ).to.equal( 'new' );
 	} );
 
 	it( 'should be fired when attribute is removed', () => {
-		const elem = new Element( 'p', [ new Attribute( 'key', 'old' ) ] );
+		const elem = new Element( 'p', { key: 'old' } );
 		root.insertChildren( 0, elem );
 
 		doc.applyOperation(
 			new AttributeOperation(
 				Range.createFromParentsAndOffsets( root, 0, elem, 0 ),
-				new Attribute( 'key', 'old' ),
+				'key',
+				'old',
 				null,
 				doc.version
 			)
@@ -138,19 +140,21 @@ describe( 'Document change event', () => {
 		expect( changes ).to.have.length( 1 );
 		expect( types[ 0 ] ).to.equal( 'attr' );
 		expect( changes[ 0 ].range ).to.deep.equal( Range.createFromParentsAndOffsets( root, 0, elem, 0 ) );
-		expect( changes[ 0 ].oldAttr ).to.deep.equal( new Attribute( 'key', 'old' ) );
-		expect( changes[ 0 ].newAttr ).to.be.null;
+		expect( changes[ 0 ].key ).to.equal( 'key' );
+		expect( changes[ 0 ].oldValue ).to.equal( 'old' );
+		expect( changes[ 0 ].newValue ).to.be.null;
 	}  );
 
 	it( 'should be fired when attribute changes', () => {
-		const elem = new Element( 'p', [ new Attribute( 'key', 'old' ) ] );
+		const elem = new Element( 'p', { key: 'old' } );
 		root.insertChildren( 0, elem );
 
 		doc.applyOperation(
 			new AttributeOperation(
 				Range.createFromParentsAndOffsets( root, 0, elem, 0 ),
-				new Attribute( 'key', 'old' ),
-				new Attribute( 'key', 'new' ),
+				'key',
+				'old',
+				'new',
 				doc.version
 			)
 		);
@@ -158,7 +162,8 @@ describe( 'Document change event', () => {
 		expect( changes ).to.have.length( 1 );
 		expect( types[ 0 ] ).to.equal( 'attr' );
 		expect( changes[ 0 ].range ).to.deep.equal( Range.createFromParentsAndOffsets( root, 0, elem, 0 ) );
-		expect( changes[ 0 ].oldAttr ).to.deep.equal( new Attribute( 'key', 'old' ) );
-		expect( changes[ 0 ].newAttr ).to.deep.equal( new Attribute( 'key', 'new' ) );
+		expect( changes[ 0 ].key ).to.equal( 'key' );
+		expect( changes[ 0 ].oldValue ).to.equal( 'old' );
+		expect( changes[ 0 ].newValue ).to.equal( 'new' );
 	}  );
 } );

+ 14 - 23
packages/ckeditor5-engine/tests/treemodel/element.js

@@ -10,7 +10,6 @@
 import Node from '/ckeditor5/core/treemodel/node.js';
 import NodeList from '/ckeditor5/core/treemodel/nodelist.js';
 import Element from '/ckeditor5/core/treemodel/element.js';
-import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 
 describe( 'Element', () => {
 	describe( 'constructor', () => {
@@ -25,7 +24,7 @@ describe( 'Element', () => {
 		} );
 
 		it( 'should create element with attributes', () => {
-			let attr = new Attribute( 'foo', 'bar' );
+			let attr = { foo: 'bar' };
 
 			let element = new Element( 'elem', [ attr ] );
 			let parent = new Element( 'parent', [], [ element ] );
@@ -34,7 +33,7 @@ describe( 'Element', () => {
 			expect( element ).to.have.property( 'name' ).that.equals( 'elem' );
 			expect( element ).to.have.property( 'parent' ).that.equals( parent );
 			expect( element._attrs.size ).to.equal( 1 );
-			expect( element.getAttributeValue( attr.key ) ).to.equal( attr.value );
+			expect( element.getAttribute( attr.key ) ).to.equal( attr.value );
 		} );
 
 		it( 'should create element with children', () => {
@@ -109,7 +108,6 @@ describe( 'Element', () => {
 
 	describe( 'attributes interface', () => {
 		let node;
-		let attr = new Attribute( 'foo', 'bar' );
 
 		beforeEach( () => {
 			node = new Element();
@@ -117,35 +115,28 @@ describe( 'Element', () => {
 
 		describe( 'setAttribute', () => {
 			it( 'should set given attribute on the element', () => {
-				node.setAttribute( attr );
+				node.setAttribute( 'foo', 'bar' );
 
-				expect( node.getAttributeValue( 'foo' ) ).to.equal( 'bar' );
-			} );
-
-			it( 'should be chainable', () => {
-				let chain = node.setAttribute( attr );
-
-				expect( chain ).to.equal( node );
+				expect( node.getAttribute( 'foo' ) ).to.equal( 'bar' );
 			} );
 		} );
 
 		describe( 'setAttributesTo', () => {
 			it( 'should remove all attributes set on element and set the given ones', () => {
-				let attr2 = new Attribute( 'abc', 'xyz' );
-				node.setAttribute( attr2 );
-				node.setAttributesTo( [ attr ] );
+				node.setAttribute( 'abc', 'xyz' );
+				node.setAttributesTo( { foo: 'bar' } );
 
-				expect( node.getAttributeValue( 'foo' ) ).to.equal( 'bar' );
-				expect( node.getAttributeValue( 'abc' ) ).to.be.null;
+				expect( node.getAttribute( 'foo' ) ).to.equal( 'bar' );
+				expect( node.getAttribute( 'abc' ) ).to.be.undefined;
 			} );
 		} );
 
 		describe( 'removeAttribute', () => {
 			it( 'should remove attribute set on the element and return true', () => {
-				node.setAttribute( attr );
+				node.setAttribute( 'foo', 'bar' );
 				let result = node.removeAttribute( 'foo' );
 
-				expect( node.getAttributeValue( 'foo' ) ).to.be.null;
+				expect( node.getAttribute( 'foo' ) ).to.be.undefined;
 				expect( result ).to.be.true;
 			} );
 
@@ -158,13 +149,13 @@ describe( 'Element', () => {
 
 		describe( 'clearAttributes', () => {
 			it( 'should remove all attributes from the element', () => {
-				node.setAttribute( attr );
-				node.setAttribute( new Attribute( 'abc', 'xyz' ) );
+				node.setAttribute( 'foo', 'bar' );
+				node.setAttribute( 'abc', 'xyz' );
 
 				node.clearAttributes();
 
-				expect( node.getAttributeValue( 'foo' ) ).to.be.null;
-				expect( node.getAttributeValue( 'abc' ) ).to.be.null;
+				expect( node.getAttribute( 'foo' ) ).to.be.undefined;
+				expect( node.getAttribute( 'abc' ) ).to.be.undefined;
 			} );
 		} );
 	} );

+ 8 - 33
packages/ckeditor5-engine/tests/treemodel/node.js

@@ -8,8 +8,6 @@
 'use strict';
 
 import Element from '/ckeditor5/core/treemodel/element.js';
-import Attribute from '/ckeditor5/core/treemodel/attribute.js';
-import AttributeList from '/ckeditor5/core/treemodel/attributelist.js';
 import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
 describe( 'Node', () => {
@@ -85,20 +83,17 @@ describe( 'Node', () => {
 		it( 'should create empty attribute list if no parameters were passed', () => {
 			let foo = new Element( 'foo' );
 
-			expect( foo._attrs ).to.be.instanceof( AttributeList );
+			expect( foo._attrs ).to.be.instanceof( Map );
 			expect( foo._attrs.size ).to.equal( 0 );
 		} );
 
 		it( 'should initialize attribute list with passed attributes', () => {
-			let attrs = [
-				new Attribute( 'foo', true ),
-				new Attribute( 'bar', false )
-			];
+			let attrs = { foo: true, bar: false };
 			let foo = new Element( 'foo', attrs );
 
 			expect( foo._attrs.size ).to.equal( 2 );
-			expect( foo.getAttributeValue( 'foo' ) ).to.equal( true );
-			expect( foo.getAttributeValue( 'bar' ) ).to.equal( false );
+			expect( foo.getAttribute( 'foo' ) ).to.equal( true );
+			expect( foo.getAttribute( 'bar' ) ).to.equal( false );
 		} );
 	} );
 
@@ -155,19 +150,9 @@ describe( 'Node', () => {
 	} );
 
 	describe( 'attributes interface', () => {
-		let attr = new Attribute( 'foo', 'bar' );
-		let attr2 = new Attribute( 'foo', 'foo' );
-		let node = new Element( 'p', [ attr ] );
+		let node = new Element( 'p', { foo: 'bar' } );
 
 		describe( 'hasAttribute', () => {
-			it( 'should return true if element contains given attribute', () => {
-				expect( node.hasAttribute( attr ) ).to.be.true;
-			} );
-
-			it( 'should return false if element does not contain given attribute', () => {
-				expect( node.hasAttribute( attr2 ) ).to.be.false;
-			} );
-
 			it( 'should return true if element contains attribute with given key', () => {
 				expect( node.hasAttribute( 'foo' ) ).to.be.true;
 			} );
@@ -178,8 +163,8 @@ describe( 'Node', () => {
 		} );
 
 		describe( 'getAttribute', () => {
-			it( 'should return attribute with given key if element contains given attribute', () => {
-				expect( node.getAttribute( 'foo' ) ).to.equal( attr );
+			it( 'should return attribute value for given key if element contains given attribute', () => {
+				expect( node.getAttribute( 'foo' ) ).to.equal( 'bar' );
 			} );
 
 			it( 'should return undefined if element does not contain given attribute', () => {
@@ -187,16 +172,6 @@ describe( 'Node', () => {
 			} );
 		} );
 
-		describe( 'getAttributeValue', () => {
-			it( 'should return attribute value for given key if element contains given attribute', () => {
-				expect( node.getAttributeValue( 'foo' ) ).to.equal( 'bar' );
-			} );
-
-			it( 'should return null if element does not contain given attribute', () => {
-				expect( node.getAttributeValue( 'bar' ) ).to.be.null;
-			} );
-		} );
-
 		describe( 'getAttributes', () => {
 			it( 'should return an iterator that iterates over all attributes set on the element', () => {
 				let it = node.getAttributes();
@@ -209,7 +184,7 @@ describe( 'Node', () => {
 					step = it.next();
 				}
 
-				expect( attrs ).to.deep.equal( [ attr ] );
+				expect( attrs ).to.deep.equal( [ [ 'foo', 'bar' ] ] );
 			} );
 		} );
 	} );

+ 17 - 20
packages/ckeditor5-engine/tests/treemodel/nodelist.js

@@ -10,7 +10,6 @@
 import NodeList from '/ckeditor5/core/treemodel/nodelist.js';
 import Element from '/ckeditor5/core/treemodel/element.js';
 import Text from '/ckeditor5/core/treemodel/text.js';
-import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
 describe( 'NodeList', () => {
@@ -41,16 +40,16 @@ describe( 'NodeList', () => {
 		} );
 
 		it( 'should change text with attribute into a set of nodes', () => {
-			let attr = new Attribute( 'bold', true );
+			let attr = { bold: true };
 			let nodeList = new NodeList( new Text( 'foo', [ attr ] ) );
 
 			expect( nodeList.length ).to.equal( 3 );
 			expect( nodeList.get( 0 ).character ).to.equal( 'f' );
-			expect( nodeList.get( 0 ).getAttributeValue( attr.key ) ).to.equal( attr.value );
+			expect( nodeList.get( 0 ).getAttribute( attr.key ) ).to.equal( attr.value );
 			expect( nodeList.get( 1 ).character ).to.equal( 'o' );
-			expect( nodeList.get( 1 ).getAttributeValue( attr.key ) ).to.equal( attr.value );
+			expect( nodeList.get( 1 ).getAttribute( attr.key ) ).to.equal( attr.value );
 			expect( nodeList.get( 2 ).character ).to.equal( 'o' );
-			expect( nodeList.get( 2 ).getAttributeValue( attr.key ) ).to.equal( attr.value );
+			expect( nodeList.get( 2 ).getAttribute( attr.key ) ).to.equal( attr.value );
 		} );
 
 		it( 'should change array of characters into a set of nodes', () => {
@@ -69,7 +68,7 @@ describe( 'NodeList', () => {
 		} );
 
 		it( 'should omit empty strings / texts', () => {
-			let nodeList = new NodeList( [ 'fo', '', 'ob', new Text( '', [ new Attribute( 'foo', true ) ] ), 'ar' ] );
+			let nodeList = new NodeList( [ 'fo', '', 'ob', new Text( '', { foo: true } ), 'ar' ] );
 
 			expect( nodeList.length ).to.equal( 6 );
 			expect( nodeList.get( 0 ).character ).to.equal( 'f' );
@@ -88,7 +87,7 @@ describe( 'NodeList', () => {
 		} );
 
 		it( 'should merge strings and text objects if possible', () => {
-			let attr = new Attribute( 'foo', 'bar' );
+			let attr = { foo: 'bar' };
 			let nodeList = new NodeList( [ 'fo', new Text( 'o' ), new Text( 'x', [ attr ] ), new Text( 'y', [ attr ] ), 'bar' ] );
 
 			expect( nodeList.length ).to.equal( 8 );
@@ -125,7 +124,7 @@ describe( 'NodeList', () => {
 		} );
 
 		it( 'should merge inserted text objects if possible', () => {
-			let attr = new Attribute( 'foo', 'bar' );
+			let attr = { foo: 'bar' };
 			let outerList = new NodeList( [ 'foo', new Text( 'bar', [ attr ] ) ] );
 			let innerList = new NodeList( [ 'x' , new Text( 'y', [ attr ] ) ] );
 
@@ -156,7 +155,7 @@ describe( 'NodeList', () => {
 		} );
 
 		it( 'should merge text objects left in node list possible', () => {
-			let attr = new Attribute( 'foo', 'bar' );
+			let attr = { foo: 'bar' };
 			let nodeList = new NodeList( [ 'foo', new Text( 'xxx', [ attr ] ), 'bar' ] );
 
 			nodeList.remove( 2, 5 );
@@ -223,13 +222,12 @@ describe( 'NodeList', () => {
 
 	describe( 'setAttribute', () => {
 		it( 'should change attribute for multiple items in node list but not for their children', () => {
-			let attr = new Attribute( 'a', true );
 			let p = new Element( 'p', [], 'x' );
 			let div = new Element( 'div' );
 
 			let nodeList = new NodeList( [ p, 'foo', div, 'bar' ] );
 
-			nodeList.setAttribute( 0, 6, attr.key, attr );
+			nodeList.setAttribute( 0, 6, 'a', 'true' );
 
 			// Attribute set.
 			expect( p.hasAttribute( 'a' ) ).to.be.true;
@@ -246,12 +244,11 @@ describe( 'NodeList', () => {
 		} );
 
 		it( 'should remove attribute if no new attribute has been passed', () => {
-			let attr = new Attribute( 'a', true );
-			let p = new Element( 'p', [ attr ] );
-			let text = new Text( 'foobar', [ attr ] );
+			let p = new Element( 'p', { a: true } );
+			let text = new Text( 'foobar', { a: true } );
 			let nodeList = new NodeList( [ p, text ] );
 
-			nodeList.setAttribute( 0, 4, attr.key, null );
+			nodeList.setAttribute( 0, 4, 'a', null );
 
 			expect( p.hasAttribute( 'a' ) ).to.be.false;
 			expect( nodeList.get( 1 ).hasAttribute( 'a' ) ).to.be.false;
@@ -264,7 +261,7 @@ describe( 'NodeList', () => {
 		} );
 
 		it( 'should throw if wrong index or number is passed', () => {
-			let attr = new Attribute( 'a', true );
+			let attr = { a: true };
 			let text = new Text( 'foo', [ attr ] );
 			let nodeList = new NodeList( text );
 
@@ -289,7 +286,7 @@ describe( 'NodeList', () => {
 		} );
 
 		it( 'should do nothing if node before and after index are different', () => {
-			let nodeList = new NodeList( [ new Text( 'ab', [ new Attribute( 'foo', true ) ] ), 'cd' ] );
+			let nodeList = new NodeList( [ new Text( 'ab', { foo: true } ), 'cd' ] );
 			nodeList._splitNodeAt( 2 );
 
 			expect( nodeList._nodes.length ).to.equal( 2 );
@@ -300,7 +297,7 @@ describe( 'NodeList', () => {
 
 	describe( '_mergeNodeAt', () => {
 		it( 'should merge two text object if they have same attributes', () => {
-			let attr = new Attribute( 'foo', true );
+			let attr = { foo: true };
 			let nodeList = new NodeList( [ 'ab', new Text( 'cd', [ attr ] ) ] );
 
 			expect( nodeList._nodes.length ).to.equal( 2 );
@@ -313,7 +310,7 @@ describe( 'NodeList', () => {
 		} );
 
 		it( 'should do nothing if text objects has different attributes', () => {
-			let nodeList = new NodeList( [ new Text( 'ab', [ new Attribute( 'foo', true ) ] ), 'cd' ] );
+			let nodeList = new NodeList( [ new Text( 'ab', { foo: true } ), 'cd' ] );
 
 			nodeList._mergeNodeAt( 2 );
 
@@ -325,7 +322,7 @@ describe( 'NodeList', () => {
 
 	describe( '_getCharIndex', () => {
 		it( 'should return offset of character at given index from the beginning of the NodeListText containing that character', () => {
-			let nodeList = new NodeList( [ new Text( 'ab', [ new Attribute( 'foo', true ) ] ), 'cd' ] );
+			let nodeList = new NodeList( [ new Text( 'ab', { foo: true } ), 'cd' ] );
 			let charIndexC = nodeList._getCharIndex( 2 );
 			let charIndexD = nodeList._getCharIndex( 3 );
 

+ 75 - 116
packages/ckeditor5-engine/tests/treemodel/operation/attributeoperation.js

@@ -12,7 +12,6 @@ import Element from '/ckeditor5/core/treemodel/element.js';
 import AttributeOperation from '/ckeditor5/core/treemodel/operation/attributeoperation.js';
 import Position from '/ckeditor5/core/treemodel/position.js';
 import Range from '/ckeditor5/core/treemodel/range.js';
-import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 import Text from '/ckeditor5/core/treemodel/text.js';
 import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
@@ -27,8 +26,9 @@ describe( 'AttributeOperation', () => {
 	it( 'should have proper type', () => {
 		const op = new AttributeOperation(
 			new Range( new Position( root, [ 0 ] ), new Position( root, [ 2 ] ) ),
+			'isNew',
 			null,
-			new Attribute( 'isNew', true ),
+			true,
 			doc.version
 		);
 
@@ -36,38 +36,34 @@ describe( 'AttributeOperation', () => {
 	} );
 
 	it( 'should insert attribute to the set of nodes', () => {
-		let newAttr = new Attribute( 'isNew', true );
-
 		root.insertChildren( 0, 'bar' );
 
 		doc.applyOperation(
 			new AttributeOperation(
 				new Range( new Position( root, [ 0 ] ), new Position( root, [ 2 ] ) ),
+				'isNew',
 				null,
-				newAttr,
+				true,
 				doc.version
 			)
 		);
 
 		expect( doc.version ).to.equal( 1 );
 		expect( root.getChildCount() ).to.equal( 3 );
-		expect( root.getChild( 0 ).hasAttribute( newAttr ) ).to.be.true;
-		expect( root.getChild( 1 ).hasAttribute( newAttr ) ).to.be.true;
+		expect( root.getChild( 0 ).hasAttribute( 'isNew' ) ).to.be.true;
+		expect( root.getChild( 1 ).hasAttribute( 'isNew' ) ).to.be.true;
 		expect( root.getChild( 2 )._attrs.size ).to.equal( 0 );
 	} );
 
 	it( 'should add attribute to the existing attributes', () => {
-		let newAttr = new Attribute( 'isNew', true );
-		let fooAttr = new Attribute( 'foo', true );
-		let barAttr = new Attribute( 'bar', true );
-
-		root.insertChildren( 0, new Text( 'x', [ fooAttr, barAttr ] ) );
+		root.insertChildren( 0, new Text( 'x', { foo: true, bar: true } ) );
 
 		doc.applyOperation(
 			new AttributeOperation(
 				new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) ),
+				'isNew',
 				null,
-				newAttr,
+				true,
 				doc.version
 			)
 		);
@@ -75,22 +71,20 @@ describe( 'AttributeOperation', () => {
 		expect( doc.version ).to.equal( 1 );
 		expect( root.getChildCount() ).to.equal( 1 );
 		expect( root.getChild( 0 )._attrs.size ).to.equal( 3 );
-		expect( root.getChild( 0 ).hasAttribute( newAttr ) ).to.be.true;
-		expect( root.getChild( 0 ).hasAttribute( fooAttr ) ).to.be.true;
-		expect( root.getChild( 0 ).hasAttribute( barAttr ) ).to.be.true;
+		expect( root.getChild( 0 ).hasAttribute( 'isNew' ) ).to.be.true;
+		expect( root.getChild( 0 ).hasAttribute( 'foo' ) ).to.be.true;
+		expect( root.getChild( 0 ).hasAttribute( 'bar' ) ).to.be.true;
 	} );
 
 	it( 'should change attribute to the set of nodes', () => {
-		let oldAttr = new Attribute( 'isNew', false );
-		let newAttr = new Attribute( 'isNew', true );
-
-		root.insertChildren( 0, new Text( 'bar', [ oldAttr ] ) );
+		root.insertChildren( 0, new Text( 'bar', { isNew: false } ) );
 
 		doc.applyOperation(
 			new AttributeOperation(
 				new Range( new Position( root, [ 0 ] ), new Position( root, [ 2 ] ) ),
-				oldAttr,
-				newAttr,
+				'isNew',
+				false,
+				true,
 				doc.version
 			)
 		);
@@ -98,26 +92,22 @@ describe( 'AttributeOperation', () => {
 		expect( doc.version ).to.equal( 1 );
 		expect( root.getChildCount() ).to.equal( 3 );
 		expect( root.getChild( 0 )._attrs.size ).to.equal( 1 );
-		expect( root.getChild( 0 ).hasAttribute( newAttr ) ).to.be.true;
+		expect( root.getChild( 0 ).getAttribute( 'isNew' ) ).to.be.true;
 		expect( root.getChild( 1 )._attrs.size ).to.equal( 1 );
-		expect( root.getChild( 1 ).hasAttribute( newAttr ) ).to.be.true;
+		expect( root.getChild( 1 ).getAttribute( 'isNew' ) ).to.be.true;
 		expect( root.getChild( 2 )._attrs.size ).to.equal( 1 );
-		expect( root.getChild( 2 ).hasAttribute( oldAttr ) ).to.be.true;
+		expect( root.getChild( 2 ).getAttribute( 'isNew' ) ).to.be.false;
 	} );
 
 	it( 'should change attribute in the middle of existing attributes', () => {
-		let fooAttr = new Attribute( 'foo', true );
-		let x1Attr = new Attribute( 'x', 1 );
-		let x2Attr = new Attribute( 'x', 2 );
-		let barAttr = new Attribute( 'bar', true );
-
-		root.insertChildren( 0, new Text( 'x', [ fooAttr, x1Attr, barAttr ] ) );
+		root.insertChildren( 0, new Text( 'x', { foo: true, x: 1, bar: true } ) );
 
 		doc.applyOperation(
 			new AttributeOperation(
 				new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) ),
-				x1Attr,
-				x2Attr,
+				'x',
+				1,
+				2,
 				doc.version
 			)
 		);
@@ -125,22 +115,19 @@ describe( 'AttributeOperation', () => {
 		expect( doc.version ).to.equal( 1 );
 		expect( root.getChildCount() ).to.equal( 1 );
 		expect( root.getChild( 0 )._attrs.size ).to.equal( 3 );
-		expect( root.getChild( 0 ).hasAttribute( fooAttr ) ).to.be.true;
-		expect( root.getChild( 0 ).hasAttribute( x2Attr ) ).to.be.true;
-		expect( root.getChild( 0 ).hasAttribute( barAttr ) ).to.be.true;
+		expect( root.getChild( 0 ).getAttribute( 'foo' ) ).to.be.true;
+		expect( root.getChild( 0 ).getAttribute( 'x' ) ).to.equal( 2 );
+		expect( root.getChild( 0 ).getAttribute( 'bar' ) ).to.be.true;
 	} );
 
 	it( 'should remove attribute', () => {
-		let fooAttr = new Attribute( 'foo', true );
-		let xAttr = new Attribute( 'x', true );
-		let barAttr = new Attribute( 'bar', true );
-
-		root.insertChildren( 0, new Text( 'x', [ fooAttr, xAttr, barAttr ] ) );
+		root.insertChildren( 0, new Text( 'x', { foo: true, x: true, bar: true } ) );
 
 		doc.applyOperation(
 			new AttributeOperation(
 				new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) ),
-				xAttr,
+				'x',
+				true,
 				null,
 				doc.version
 			)
@@ -149,33 +136,31 @@ describe( 'AttributeOperation', () => {
 		expect( doc.version ).to.equal( 1 );
 		expect( root.getChildCount() ).to.equal( 1 );
 		expect( root.getChild( 0 )._attrs.size ).to.equal( 2 );
-		expect( root.getChild( 0 ).hasAttribute( fooAttr ) ).to.be.true;
-		expect( root.getChild( 0 ).hasAttribute( barAttr ) ).to.be.true;
+		expect( root.getChild( 0 ).hasAttribute( 'foo' ) ).to.be.true;
+		expect( root.getChild( 0 ).hasAttribute( 'bar' ) ).to.be.true;
 	} );
 
 	it( 'should create an AttributeOperation as a reverse', () => {
-		let oldAttr = new Attribute( 'x', 'old' );
-		let newAttr = new Attribute( 'x', 'new' );
 		let range = new Range( new Position( root, [ 0 ] ), new Position( root, [ 3 ] ) );
-		let operation = new AttributeOperation( range, oldAttr, newAttr, doc.version );
+		let operation = new AttributeOperation( range, 'x', 'old', 'new', doc.version );
 		let reverse = operation.getReversed();
 
 		expect( reverse ).to.be.an.instanceof( AttributeOperation );
 		expect( reverse.baseVersion ).to.equal( 1 );
 		expect( reverse.range.isEqual( range ) ).to.be.true;
-		expect( reverse.oldAttr ).to.equal( newAttr );
-		expect( reverse.newAttr ).to.equal( oldAttr );
+		expect( reverse.key ).to.equal( 'x' );
+		expect( reverse.oldValue ).to.equal( 'new' );
+		expect( reverse.newValue ).to.equal( 'old' );
 	} );
 
 	it( 'should undo adding attribute by applying reverse operation', () => {
-		let newAttr = new Attribute( 'isNew', true );
-
 		root.insertChildren( 0, 'bar' );
 
 		let operation = new AttributeOperation(
 			new Range( new Position( root, [ 0 ] ), new Position( root, [ 3 ] ) ),
+			'isNew',
 			null,
-			newAttr,
+			true,
 			doc.version
 		);
 
@@ -192,8 +177,6 @@ describe( 'AttributeOperation', () => {
 	} );
 
 	it( 'should not set attribute of element if change range starts in the middle of that element', () => {
-		let fooAttr = new Attribute( 'foo', true );
-
 		let eleA = new Element( 'a', [], 'abc' );
 		let eleB = new Element( 'b', [], 'xyz' );
 
@@ -202,72 +185,70 @@ describe( 'AttributeOperation', () => {
 		doc.applyOperation(
 			new AttributeOperation(
 				new Range( new Position( root, [ 0, 2 ] ), new Position( root, [ 1, 2 ] ) ),
+				'foo',
 				null,
-				fooAttr,
+				true,
 				doc.version
 			)
 		);
 
-		expect( root.getChild( 0 ).hasAttribute( fooAttr ) ).to.be.false;
+		expect( root.getChild( 0 ).hasAttribute( 'foo' ) ).to.be.false;
 	} );
 
 	it( 'should not remove attribute of element if change range starts in the middle of that element', () => {
-		let fooAttr = new Attribute( 'foo', true );
+		let fooAttr = { foo: true };
 
-		let eleA = new Element( 'a', [ fooAttr ], 'abc' );
-		let eleB = new Element( 'b', [ fooAttr ], 'xyz' );
+		let eleA = new Element( 'a', fooAttr, 'abc' );
+		let eleB = new Element( 'b', fooAttr, 'xyz' );
 
 		root.insertChildren( 0, [ eleA, eleB ] );
 
 		doc.applyOperation(
 			new AttributeOperation(
 				new Range( new Position( root, [ 0, 3 ] ), new Position( root, [ 1, 0 ] ) ),
-				fooAttr,
+				'foo',
+				true,
 				null,
 				doc.version
 			)
 		);
 
-		expect( root.getChild( 0 ).hasAttribute( fooAttr ) ).to.be.true;
+		expect( root.getChild( 0 ).hasAttribute( 'foo' ) ).to.be.true;
 	} );
 
 	it( 'should undo changing attribute by applying reverse operation', () => {
-		let oldAttr = new Attribute( 'isNew', false );
-		let newAttr = new Attribute( 'isNew', true );
-
-		root.insertChildren( 0, new Text( 'bar', [ oldAttr ] ) );
+		root.insertChildren( 0, new Text( 'bar', { isNew: false } ) );
 
 		let operation = new AttributeOperation(
 			new Range( new Position( root, [ 0 ] ), new Position( root, [ 3 ] ) ),
-			oldAttr,
-			newAttr,
+			'isNew',
+			false,
+			true,
 			doc.version
 		);
 
 		let reverse = operation.getReversed();
 
 		doc.applyOperation( operation );
-
 		doc.applyOperation( reverse );
 
 		expect( doc.version ).to.equal( 2 );
 		expect( root.getChildCount() ).to.equal( 3 );
 		expect( root.getChild( 0 )._attrs.size ).to.equal( 1 );
-		expect( root.getChild( 0 ).hasAttribute( oldAttr ) ).to.be.true;
+		expect( root.getChild( 0 ).getAttribute( 'isNew' ) ).to.be.false;
 		expect( root.getChild( 1 )._attrs.size ).to.equal( 1 );
-		expect( root.getChild( 1 ).hasAttribute( oldAttr ) ).to.be.true;
+		expect( root.getChild( 1 ).getAttribute( 'isNew' ) ).to.be.false;
 		expect( root.getChild( 2 )._attrs.size ).to.equal( 1 );
-		expect( root.getChild( 2 ).hasAttribute( oldAttr ) ).to.be.true;
+		expect( root.getChild( 2 ).getAttribute( 'isNew' ) ).to.be.false;
 	} );
 
 	it( 'should undo remove attribute by applying reverse operation', () => {
-		let fooAttr = new Attribute( 'foo', false );
-
-		root.insertChildren( 0, new Text( 'bar', [ fooAttr ] ) );
+		root.insertChildren( 0, new Text( 'bar', { foo: true } ) );
 
 		let operation = new AttributeOperation(
 			new Range( new Position( root, [ 0 ] ), new Position( root, [ 3 ] ) ),
-			fooAttr,
+			'foo',
+			true,
 			null,
 			doc.version
 		);
@@ -275,29 +256,27 @@ describe( 'AttributeOperation', () => {
 		let reverse = operation.getReversed();
 
 		doc.applyOperation( operation );
-
 		doc.applyOperation( reverse );
 
 		expect( doc.version ).to.equal( 2 );
 		expect( root.getChildCount() ).to.equal( 3 );
 		expect( root.getChild( 0 )._attrs.size ).to.equal( 1 );
-		expect( root.getChild( 0 ).hasAttribute( fooAttr ) ).to.be.true;
+		expect( root.getChild( 0 ).getAttribute( 'foo' ) ).to.be.true;
 		expect( root.getChild( 1 )._attrs.size ).to.equal( 1 );
-		expect( root.getChild( 1 ).hasAttribute( fooAttr ) ).to.be.true;
+		expect( root.getChild( 1 ).getAttribute( 'foo' ) ).to.be.true;
 		expect( root.getChild( 2 )._attrs.size ).to.equal( 1 );
-		expect( root.getChild( 2 ).hasAttribute( fooAttr ) ).to.be.true;
+		expect( root.getChild( 2 ).getAttribute( 'foo' ) ).to.be.true;
 	} );
 
 	it( 'should throw an error when one try to remove and the attribute does not exists', () => {
-		let fooAttr = new Attribute( 'foo', true );
-
 		root.insertChildren( 0, 'x' );
 
 		expect( () => {
 			doc.applyOperation(
 				new AttributeOperation(
 					new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) ),
-					fooAttr,
+					'foo',
+					true,
 					null,
 					doc.version
 				)
@@ -306,48 +285,26 @@ describe( 'AttributeOperation', () => {
 	} );
 
 	it( 'should throw an error when one try to insert and the attribute already exists', () => {
-		let x1Attr = new Attribute( 'x', 1 );
-		let x2Attr = new Attribute( 'x', 2 );
-
-		root.insertChildren( 0, new Text( 'x', [ x1Attr ] ) );
+		root.insertChildren( 0, new Text( 'x', { x: 1 } ) );
 
 		expect( () => {
 			doc.applyOperation(
 				new AttributeOperation(
 					new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) ),
+					'x',
 					null,
-					x2Attr,
+					2,
 					doc.version
 				)
 			);
 		} ).to.throw( CKEditorError, /operation-attribute-attr-exists/ );
 	} );
 
-	it( 'should throw an error when one try to change and the new and old attributes have different keys', () => {
-		let fooAttr = new Attribute( 'foo', true );
-		let barAttr = new Attribute( 'bar', true );
-
-		root.insertChildren( 0, 'x' );
-
-		expect( () => {
-			doc.applyOperation(
-				new AttributeOperation(
-					new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) ),
-					fooAttr,
-					barAttr,
-					doc.version
-				)
-			);
-		} ).to.throw( CKEditorError, /operation-attribute-different-keys/ );
-	} );
-
 	it( 'should create an AttributeOperation with the same parameters when cloned', () => {
 		let range = new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) );
-		let oldAttr = new Attribute( 'foo', 'old' );
-		let newAttr = new Attribute( 'foo', 'bar' );
 		let baseVersion = doc.version;
 
-		let op = new AttributeOperation( range, oldAttr, newAttr, baseVersion );
+		let op = new AttributeOperation( range, 'foo', 'old', 'new', baseVersion );
 
 		let clone = op.clone();
 
@@ -356,23 +313,25 @@ describe( 'AttributeOperation', () => {
 
 		expect( clone ).to.be.instanceof( AttributeOperation );
 		expect( clone.range.isEqual( range ) ).to.be.true;
-		expect( clone.oldAttr.isEqual( oldAttr ) ).to.be.true;
-		expect( clone.newAttr.isEqual( newAttr ) ).to.be.true;
+		expect( clone.key ).to.equal( 'foo' );
+		expect( clone.oldValue ).to.equal( 'old' );
+		expect( clone.newValue ).to.equal( 'new' );
 		expect( clone.baseVersion ).to.equal( baseVersion );
 	} );
 
 	it( 'should merge characters in node list', () => {
-		let attrA = new Attribute( 'foo', 'a' );
-		let attrB = new Attribute( 'foo', 'b' );
+		let attrA = { foo: 'a' };
+		let attrB = { foo: 'b' };
 
-		root.insertChildren( 0, new Text( 'abc', [ attrA ] ) );
-		root.insertChildren( 3, new Text( 'xyz', [ attrB ] ) );
+		root.insertChildren( 0, new Text( 'abc', attrA ) );
+		root.insertChildren( 3, new Text( 'xyz', attrB ) );
 
 		doc.applyOperation(
 			new AttributeOperation(
 				new Range( new Position( root, [ 1 ] ), new Position( root, [ 3 ] ) ),
-				attrA,
-				attrB,
+				'foo',
+				'a',
+				'b',
 				doc.version
 			)
 		);

+ 65 - 49
packages/ckeditor5-engine/tests/treemodel/operation/transform.js

@@ -11,7 +11,6 @@ import RootElement from '/ckeditor5/core/treemodel/rootelement.js';
 import Node from '/ckeditor5/core/treemodel/node.js';
 import Position from '/ckeditor5/core/treemodel/position.js';
 import Range from '/ckeditor5/core/treemodel/range.js';
-import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 import transform from '/ckeditor5/core/treemodel/operation/transform.js';
 import InsertOperation from '/ckeditor5/core/treemodel/operation/insertoperation.js';
 import AttributeOperation from '/ckeditor5/core/treemodel/operation/attributeoperation.js';
@@ -169,8 +168,9 @@ describe( 'transform', () => {
 			it( 'no position update', () => {
 				let transformBy = new AttributeOperation(
 					Range.createFromPositionAndShift( position, 2 ),
+					'foo',
 					null,
-					new Attribute( 'foo', 'bar' ),
+					'bar',
 					baseVersion
 				);
 
@@ -385,19 +385,14 @@ describe( 'transform', () => {
 	} );
 
 	describe( 'AttributeOperation', () => {
-		let start, end, range, oldAttr, newAttr, anotherOldAttr, anotherNewAttr;
+		let start, end, range;
 
 		beforeEach( () => {
-			oldAttr = new Attribute( 'foo', 'abc' );
-			newAttr = new Attribute( 'foo', 'bar' );
-
-			anotherOldAttr = new Attribute( oldAttr.key, 'another' );
-			anotherNewAttr = new Attribute( oldAttr.key, 'anothernew' );
-
 			expected = {
 				type: AttributeOperation,
-				oldAttr: oldAttr,
-				newAttr: newAttr,
+				key: 'foo',
+				oldValue: 'abc',
+				newValue: 'bar',
 				baseVersion: baseVersion + 1
 			};
 		} );
@@ -409,7 +404,7 @@ describe( 'transform', () => {
 
 				range = new Range( start, end );
 
-				op = new AttributeOperation( range, oldAttr, newAttr, baseVersion );
+				op = new AttributeOperation( range, 'foo', 'abc', 'bar', baseVersion );
 
 				expected.range = new Range( start, end );
 			} );
@@ -527,8 +522,9 @@ describe( 'transform', () => {
 				it( 'attributes have different key: no operation update', () => {
 					let transformBy = new AttributeOperation(
 						Range.createFromRange( range ),
-						new Attribute( 'abc', true ),
-						new Attribute( 'abc', false ),
+						'abc',
+						true,
+						false,
 						baseVersion
 					);
 
@@ -538,43 +534,52 @@ describe( 'transform', () => {
 					expectOperation( transOp[ 0 ], expected );
 				} );
 
-				it( 'attributes set same value: no operation update', () => {
+				it( 'same key and value: convert to NoOperation', () => {
 					let transformBy = new AttributeOperation(
 						Range.createFromRange( range ),
-						oldAttr,
-						newAttr,
+						'foo',
+						'abc',
+						'bar',
 						baseVersion
 					);
 
 					let transOp = transform( op, transformBy );
 
 					expect( transOp.length ).to.equal( 1 );
-					expectOperation( transOp[ 0 ], expected );
+					expectOperation( transOp[ 0 ], {
+						type: NoOperation,
+						baseVersion: baseVersion + 1
+					} );
 				} );
 
-				it( 'both operations removes attribute: no operation update', () => {
-					op.newAttr = null;
+				it( 'both operations removes same attribute: convert to NoOperation', () => {
+					op.newValue = null;
 
 					let transformBy = new AttributeOperation(
 						new Range( new Position( root, [ 1, 1, 4 ] ), new Position( root, [ 3 ] ) ),
-						anotherOldAttr,
+						'foo',
+						'another',
 						null,
 						baseVersion
 					);
 
 					let transOp = transform( op, transformBy, true );
 
-					expected.newAttr = null;
+					expected.newValue = null;
 
 					expect( transOp.length ).to.equal( 1 );
-					expectOperation( transOp[ 0 ], expected );
+					expectOperation( transOp[ 0 ], {
+						type: NoOperation,
+						baseVersion: baseVersion + 1
+					} );
 				} );
 
 				describe( 'that is less important and', () => {
 					it( 'range does not intersect original range: no operation update', () => {
 						let transformBy = new AttributeOperation(
 							new Range( new Position( root, [ 3, 0 ] ), new Position( root, [ 4 ] ) ),
-							anotherOldAttr,
+							'foo',
+							'another',
 							null,
 							baseVersion
 						);
@@ -585,32 +590,34 @@ describe( 'transform', () => {
 						expectOperation( transOp[ 0 ], expected );
 					} );
 
-					it( 'range contains original range: update oldAttr', () => {
+					it( 'range contains original range: update oldValue', () => {
 						let transformBy = new AttributeOperation(
 							new Range( new Position( root, [ 1, 1, 4 ] ), new Position( root, [ 3 ] ) ),
-							anotherOldAttr,
+							'foo',
+							'another',
 							null,
 							baseVersion
 						);
 
 						let transOp = transform( op, transformBy, true );
 
-						expected.oldAttr = anotherOldAttr;
+						expected.oldValue = 'another';
 
 						expect( transOp.length ).to.equal( 1 );
 						expectOperation( transOp[ 0 ], expected );
 					} );
 
 					// [ original range   <   ]   range transformed by >
-					it( 'range intersects on left: split into two operations, update oldAttr', () => {
+					it( 'range intersects on left: split into two operations, update oldValue', () => {
 						// Get more test cases and better code coverage
-						op.newAttr = null;
+						op.newValue = null;
 
 						let transformBy = new AttributeOperation(
 							new Range( new Position( root, [ 1, 4, 2 ] ), new Position( root, [ 3 ] ) ),
-							anotherOldAttr,
+							'foo',
+							'another',
 							// Get more test cases and better code coverage
-							anotherNewAttr,
+							'anothernew',
 							baseVersion
 						);
 
@@ -618,7 +625,7 @@ describe( 'transform', () => {
 
 						expect( transOp.length ).to.equal( 2 );
 
-						expected.newAttr = null;
+						expected.newValue = null;
 
 						expected.range.end.path = [ 1, 4, 2 ];
 
@@ -626,17 +633,18 @@ describe( 'transform', () => {
 
 						expected.range.start.path = [ 1, 4, 2 ];
 						expected.range.end = op.range.end;
-						expected.oldAttr = anotherOldAttr;
+						expected.oldValue = 'another';
 						expected.baseVersion++;
 
 						expectOperation( transOp[ 1 ], expected );
 					} );
 
 					// [  range transformed by  <   ]  original range  >
-					it( 'range intersects on right: split into two operations, update oldAttr', () => {
+					it( 'range intersects on right: split into two operations, update oldValue', () => {
 						let transformBy = new AttributeOperation(
 							new Range( new Position( root, [ 1 ] ), new Position( root, [ 2, 1 ] ) ),
-							anotherOldAttr,
+							'foo',
+							'another',
 							null,
 							baseVersion
 						);
@@ -651,16 +659,17 @@ describe( 'transform', () => {
 
 						expected.range.start = op.range.start;
 						expected.range.end.path = [ 2, 1 ];
-						expected.oldAttr = anotherOldAttr;
+						expected.oldValue = 'another';
 						expected.baseVersion++;
 
 						expectOperation( transOp[ 1 ], expected );
 					} );
 
-					it( 'range is inside original range: split into three operations, update oldAttr', () => {
+					it( 'range is inside original range: split into three operations, update oldValue', () => {
 						let transformBy = new AttributeOperation(
 							new Range( new Position( root, [ 1, 4, 1 ] ), new Position( root, [ 2, 1 ] ) ),
-							anotherOldAttr,
+							'foo',
+							'another',
 							null,
 							baseVersion
 						);
@@ -681,7 +690,7 @@ describe( 'transform', () => {
 
 						expected.range.start.path = [ 1, 4, 1 ];
 						expected.range.end.path = [ 2, 1 ];
-						expected.oldAttr = anotherOldAttr;
+						expected.oldValue = 'another';
 						expected.baseVersion++;
 
 						expectOperation( transOp[ 2 ], expected );
@@ -692,7 +701,8 @@ describe( 'transform', () => {
 					it( 'range does not intersect original range: no operation update', () => {
 						let transformBy = new AttributeOperation(
 							new Range( new Position( root, [ 3, 0 ] ), new Position( root, [ 4 ] ) ),
-							oldAttr,
+							'foo',
+							'abc',
 							null,
 							baseVersion
 						);
@@ -706,7 +716,8 @@ describe( 'transform', () => {
 					it( 'range contains original range: convert to NoOperation', () => {
 						let transformBy = new AttributeOperation(
 							new Range( new Position( root, [ 1, 1, 4 ] ), new Position( root, [ 3 ] ) ),
-							oldAttr,
+							'foo',
+							'abc',
 							null,
 							baseVersion
 						);
@@ -724,7 +735,8 @@ describe( 'transform', () => {
 					it( 'range intersects on left: shrink original range', () => {
 						let transformBy = new AttributeOperation(
 							new Range( new Position( root, [ 1, 4, 2 ] ), new Position( root, [ 3 ] ) ),
-							oldAttr,
+							'foo',
+							'abc',
 							null,
 							baseVersion
 						);
@@ -741,7 +753,8 @@ describe( 'transform', () => {
 					it( 'range intersects on right: shrink original range', () => {
 						let transformBy = new AttributeOperation(
 							new Range( new Position( root, [ 1 ] ), new Position( root, [ 2, 1 ] ) ),
-							oldAttr,
+							'foo',
+							'abc',
 							null,
 							baseVersion
 						);
@@ -757,7 +770,8 @@ describe( 'transform', () => {
 					it( 'range is inside original range: split into two operations', () => {
 						let transformBy = new AttributeOperation(
 							new Range( new Position( root, [ 1, 4, 1 ] ), new Position( root, [ 2, 1 ] ) ),
-							oldAttr,
+							'foo',
+							'abc',
 							null,
 							baseVersion
 						);
@@ -1048,7 +1062,7 @@ describe( 'transform', () => {
 
 				range = new Range( start, end );
 
-				op = new AttributeOperation( range, oldAttr, newAttr, baseVersion );
+				op = new AttributeOperation( range, 'foo', 'abc', 'bar', baseVersion );
 
 				expected.range = new Range( start, end );
 			} );
@@ -1515,8 +1529,9 @@ describe( 'transform', () => {
 			it( 'no operation update', () => {
 				let transformBy = new AttributeOperation(
 					new Range( sourcePosition, rangeEnd ),
-					new Attribute( 'abc', true ),
-					new Attribute( 'abc', false ),
+					'abc',
+					true,
+					false,
 					baseVersion
 				);
 
@@ -2314,8 +2329,9 @@ describe( 'transform', () => {
 						new Position( root, [ 0 ] ),
 						new Position( root, [ 1 ] )
 					),
-					new Attribute( 'foo', 'bar' ),
-					new Attribute( 'foo', 'xyz' ),
+					'foo',
+					'bar',
+					'xyz',
 					baseVersion
 				);
 

+ 20 - 46
packages/ckeditor5-engine/tests/treemodel/selection.js

@@ -8,8 +8,6 @@
 'use strict';
 
 import Document from '/ckeditor5/core/treemodel/document.js';
-import Attribute from '/ckeditor5/core/treemodel/attribute.js';
-import AttributeList from '/ckeditor5/core/treemodel/attributelist.js';
 import Element from '/ckeditor5/core/treemodel/element.js';
 import Range from '/ckeditor5/core/treemodel/range.js';
 import Position from '/ckeditor5/core/treemodel/position.js';
@@ -23,7 +21,7 @@ describe( 'Selection', () => {
 	let attrFooBar;
 
 	before( () => {
-		attrFooBar = new Attribute( 'foo', 'bar' );
+		attrFooBar = { foo: 'bar' };
 	} );
 
 	let doc, root, selection, liveRange, range;
@@ -48,7 +46,7 @@ describe( 'Selection', () => {
 		expect( ranges.length ).to.equal( 0 );
 		expect( selection.anchor ).to.be.null;
 		expect( selection.focus ).to.be.null;
-		expect( selection._attrs ).to.be.instanceof( AttributeList );
+		expect( selection._attrs ).to.be.instanceof( Map );
 		expect( selection._attrs.size ).to.equal( 0 );
 	} );
 
@@ -413,60 +411,36 @@ describe( 'Selection', () => {
 	} );
 
 	describe( 'attributes interface', () => {
-		let attr = new Attribute( 'foo', 'bar' );
-		let attr2 = new Attribute( 'foo', 'foo' );
-
 		describe( 'setAttribute', () => {
 			it( 'should set given attribute on the selection', () => {
-				selection.setAttribute( attr );
+				selection.setAttribute( 'foo', 'bar' );
 
-				expect( selection.getAttribute( 'foo' ) ).to.equal( attr );
+				expect( selection.getAttribute( 'foo' ) ).to.equal( 'bar' );
 			} );
 		} );
 
 		describe( 'hasAttribute', () => {
-			it( 'should return true if element contains given attribute', () => {
-				selection.setAttribute( attr );
-
-				expect( selection.hasAttribute( attr ) ).to.be.true;
-			} );
-
-			it( 'should return false if element does not contain given attribute', () => {
-				expect( selection.hasAttribute( attr2 ) ).to.be.false;
-			} );
-
 			it( 'should return true if element contains attribute with given key', () => {
-				selection.setAttribute( attr );
+				selection.setAttribute( 'foo', 'bar' );
 
 				expect( selection.hasAttribute( 'foo' ) ).to.be.true;
 			} );
 
 			it( 'should return false if element does not contain attribute with given key', () => {
-				expect( selection.hasAttribute( 'bar' ) ).to.be.false;
+				expect( selection.hasAttribute( 'abc' ) ).to.be.false;
 			} );
 		} );
 
 		describe( 'getAttribute', () => {
 			it( 'should return undefined if element does not contain given attribute', () => {
-				expect( selection.getAttribute( 'bar' ) ).to.be.undefined;
-			} );
-		} );
-
-		describe( 'getAttributeValue', () => {
-			it( 'should return attribute value for given key if element contains given attribute', () => {
-				selection.setAttribute( attr );
-
-				expect( selection.getAttributeValue( 'foo' ) ).to.equal( 'bar' );
-			} );
-
-			it( 'should return null if element does not contain given attribute', () => {
-				expect( selection.getAttributeValue( 'bar' ) ).to.be.null;
+				expect( selection.getAttribute( 'abc' ) ).to.be.undefined;
 			} );
 		} );
 
 		describe( 'getAttributes', () => {
 			it( 'should return an iterator that iterates over all attributes set on the text fragment', () => {
-				selection.setAttribute( attr );
+				selection.setAttribute( 'foo', 'bar' );
+				selection.setAttribute( 'abc', 'xyz' );
 
 				let it = selection.getAttributes();
 				let attrs = [];
@@ -478,26 +452,26 @@ describe( 'Selection', () => {
 					step = it.next();
 				}
 
-				expect( attrs ).to.deep.equal( [ attr ] );
+				expect( attrs ).to.deep.equal( [ [ 'foo', 'bar' ], [ 'abc', 'xyz' ] ] );
 			} );
 		} );
 
 		describe( 'setAttributesTo', () => {
 			it( 'should remove all attributes set on element and set the given ones', () => {
-				selection.setAttribute( attr2 );
-				selection.setAttributesTo( [ attr ] );
+				selection.setAttribute( 'abc', 'xyz' );
+				selection.setAttributesTo( { foo: 'bar' } );
 
-				expect( selection.getAttributeValue( 'foo' ) ).to.equal( 'bar' );
-				expect( selection.getAttributeValue( 'abc' ) ).to.be.null;
+				expect( selection.getAttribute( 'foo' ) ).to.equal( 'bar' );
+				expect( selection.getAttribute( 'abc' ) ).to.be.undefined;
 			} );
 		} );
 
 		describe( 'removeAttribute', () => {
 			it( 'should remove attribute set on the text fragment and return true', () => {
-				selection.setAttribute( attr );
+				selection.setAttribute( 'foo', 'bar' );
 				let result = selection.removeAttribute( 'foo' );
 
-				expect( selection.getAttributeValue( 'foo' ) ).to.be.null;
+				expect( selection.getAttribute( 'foo' ) ).to.be.undefined;
 				expect( result ).to.be.true;
 			} );
 
@@ -510,13 +484,13 @@ describe( 'Selection', () => {
 
 		describe( 'clearAttributes', () => {
 			it( 'should remove all attributes from the element', () => {
-				selection.setAttribute( attr );
-				selection.setAttribute( new Attribute( 'abc', 'xyz' ) );
+				selection.setAttribute( 'foo', 'bar' );
+				selection.setAttribute( 'abc', 'xyz' );
 
 				selection.clearAttributes();
 
-				expect( selection.getAttributeValue( 'foo' ) ).to.be.null;
-				expect( selection.getAttributeValue( 'abc' ) ).to.be.null;
+				expect( selection.getAttribute( 'foo' ) ).to.be.undefined;
+				expect( selection.getAttribute( 'abc' ) ).to.be.undefined;
 			} );
 		} );
 	} );

+ 3 - 6
packages/ckeditor5-engine/tests/treemodel/text.js

@@ -8,18 +8,15 @@
 'use strict';
 
 import Text from '/ckeditor5/core/treemodel/text.js';
-import Attribute from '/ckeditor5/core/treemodel/attribute.js';
-import AttributeList from '/ckeditor5/core/treemodel/attributelist.js';
 
 describe( 'Text', () => {
 	describe( 'constructor', () => {
 		it( 'should create character without attributes', () => {
-			let attrs = [ new Attribute( 'bold', true ) ];
-			let text = new Text( 'bar', attrs );
+			let text = new Text( 'bar', { bold: true } );
 
 			expect( text ).to.have.property( 'text' ).that.equals( 'bar' );
-			expect( text ).to.have.property( '_attrs' ).that.is.instanceof( AttributeList );
-			expect( Array.from( text._attrs ) ).to.deep.equal( attrs );
+			expect( text ).to.have.property( '_attrs' ).that.is.instanceof( Map );
+			expect( Array.from( text._attrs ) ).to.deep.equal( [ [ 'bold', true ] ] );
 		} );
 
 		it( 'should create empty text object', () => {

+ 5 - 50
packages/ckeditor5-engine/tests/treemodel/textfragment.js

@@ -9,24 +9,22 @@
 
 import Element from '/ckeditor5/core/treemodel/element.js';
 import Text from '/ckeditor5/core/treemodel/text.js';
-import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 import TextFragment from '/ckeditor5/core/treemodel/textfragment.js';
 import Document from '/ckeditor5/core/treemodel/document.js';
 import CharacterProxy from '/ckeditor5/core/treemodel/characterproxy.js';
 
 describe( 'TextFragment', () => {
-	let doc, attr, text, element, textFragment, root;
+	let doc, text, element, textFragment, root;
 
 	before( () => {
 		doc = new Document();
 		root = doc.createRoot( 'root' );
 		element = new Element( 'div' );
 		root.insertChildren( 0, element );
-		attr = new Attribute( 'foo', 'bar' );
 	} );
 
 	beforeEach( () => {
-		text = new Text( 'foobar', [ attr ] );
+		text = new Text( 'foobar', { foo: 'bar' } );
 		element.insertChildren( 0, text );
 		textFragment = new TextFragment( element.getChild( 2 ), 3 );
 	} );
@@ -59,7 +57,7 @@ describe( 'TextFragment', () => {
 
 			expect( char ).to.be.instanceof( CharacterProxy );
 			expect( char.character ).to.equal( 'b' );
-			expect( char.getAttributeValue( 'foo' ) ).to.equal( 'bar' );
+			expect( char.getAttribute( 'foo' ) ).to.equal( 'bar' );
 			expect( char.parent ).to.equal( element );
 		} );
 
@@ -70,17 +68,7 @@ describe( 'TextFragment', () => {
 	} );
 
 	describe( 'attributes interface', () => {
-		let attr2 = new Attribute( 'abc', 'xyz' );
-
 		describe( 'hasAttribute', () => {
-			it( 'should return true if text fragment has given attribute', () => {
-				expect( textFragment.hasAttribute( attr ) ).to.be.true;
-			} );
-
-			it( 'should return false if text fragment does not have attribute', () => {
-				expect( textFragment.hasAttribute( attr2 ) ).to.be.false;
-			} );
-
 			it( 'should return true if text fragment has attribute with given key', () => {
 				expect( textFragment.hasAttribute( 'foo' ) ).to.be.true;
 			} );
@@ -92,7 +80,7 @@ describe( 'TextFragment', () => {
 
 		describe( 'getAttribute', () => {
 			it( 'should return attribute with given key if text fragment has given attribute', () => {
-				expect( textFragment.getAttribute( 'foo' ) ).to.equal( attr );
+				expect( textFragment.getAttribute( 'foo' ) ).to.equal( 'bar' );
 			} );
 
 			it( 'should return null if text fragment does not have given attribute', () => {
@@ -100,16 +88,6 @@ describe( 'TextFragment', () => {
 			} );
 		} );
 
-		describe( 'getAttributeValue', () => {
-			it( 'should return attribute value for given key if text fragment has given attribute', () => {
-				expect( textFragment.getAttributeValue( 'foo' ) ).to.equal( 'bar' );
-			} );
-
-			it( 'should return null if text fragment does not have given attribute', () => {
-				expect( textFragment.getAttributeValue( 'bar' ) ).to.be.null;
-			} );
-		} );
-
 		describe( 'getAttributes', () => {
 			it( 'should return an iterator that iterates over all attributes set on the text fragment', () => {
 				let it = textFragment.getAttributes();
@@ -122,30 +100,7 @@ describe( 'TextFragment', () => {
 					step = it.next();
 				}
 
-				expect( attrs ).to.deep.equal( [ attr ] );
-			} );
-		} );
-
-		describe( 'setAttribute', () => {
-			it( 'should set given attribute on the text fragment', () => {
-				textFragment.setAttribute( new Attribute( 'abc', 'xyz' ) );
-
-				expect( textFragment.getAttributeValue( 'abc' ) ).to.equal( 'xyz' );
-			} );
-		} );
-
-		describe( 'removeAttribute', () => {
-			it( 'should remove attribute set on the text fragment and return true', () => {
-				let result = textFragment.removeAttribute( 'foo' );
-
-				expect( textFragment.getAttributeValue( 'foo' ) ).to.be.null;
-				expect( result ).to.be.true;
-			} );
-
-			it( 'should return false if text fragment does not have given attribute', () => {
-				let result = textFragment.removeAttribute( 'abc' );
-
-				expect( result ).to.be.false;
+				expect( attrs ).to.deep.equal( [ [ 'foo', 'bar' ] ] );
 			} );
 		} );
 	} );

+ 5 - 8
packages/ckeditor5-engine/tests/treemodel/treewalker.js

@@ -8,7 +8,6 @@
 'use strict';
 
 import Document from '/ckeditor5/core/treemodel/document.js';
-import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 import Element from '/ckeditor5/core/treemodel/element.js';
 import Text from '/ckeditor5/core/treemodel/text.js';
 import TreeWalker from '/ckeditor5/core/treemodel/treewalker.js';
@@ -34,10 +33,8 @@ describe( 'range iterator', () => {
 		//     |
 		//     |- X
 
-		let attrBoldTrue = new Attribute( 'bold', true );
-
-		b = new Text( 'b', [ attrBoldTrue ] );
-		a = new Text( 'a', [ attrBoldTrue ] );
+		b = new Text( 'b', { bold: true } );
+		a = new Text( 'a', { bold: true } );
 		r = new Text( 'r' );
 		img2 = new Element( 'img2' );
 		x = new Text( 'x' );
@@ -51,8 +48,8 @@ describe( 'range iterator', () => {
 			{ type: 'ELEMENT_START', item: img1 },
 			{ type: 'ELEMENT_END', item: img1 },
 			{ type: 'ELEMENT_START', item: paragraph },
-			{ type: 'CHARACTER', text: 'b', attrs: [ attrBoldTrue ] },
-			{ type: 'CHARACTER', text: 'a', attrs: [ attrBoldTrue ] },
+			{ type: 'CHARACTER', text: 'b', attrs: [ [ 'bold', true ] ] },
+			{ type: 'CHARACTER', text: 'a', attrs: [ [ 'bold', true ] ] },
 			{ type: 'CHARACTER', text: 'r', attrs: [] },
 			{ type: 'ELEMENT_START', item: img2 },
 			{ type: 'ELEMENT_END', item: img2 },
@@ -64,7 +61,7 @@ describe( 'range iterator', () => {
 			{ type: 'ELEMENT_START', item: img1 },
 			{ type: 'ELEMENT_END', item: img1 },
 			{ type: 'ELEMENT_START', item: paragraph },
-			{ type: 'TEXT', text: 'ba', attrs: [ attrBoldTrue ] },
+			{ type: 'TEXT', text: 'ba', attrs: [ [ 'bold', true ] ] },
 			{ type: 'TEXT', text: 'r', attrs: [] },
 			{ type: 'ELEMENT_START', item: img2 },
 			{ type: 'ELEMENT_END', item: img2 },