Parcourir la source

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

Szymon Cofalik il y a 10 ans
Parent
commit
7361a2d694
30 fichiers modifiés avec 392 ajouts et 1102 suppressions
  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 },