8
0
Quellcode durchsuchen

Changed AttributeList API.

Szymon Cofalik vor 10 Jahren
Ursprung
Commit
747aa1fcbb
22 geänderte Dateien mit 345 neuen und 562 gelöschten Zeilen
  1. 93 56
      packages/ckeditor5-engine/src/treemodel/attributelist.js
  2. 3 3
      packages/ckeditor5-engine/src/treemodel/delta/attributedelta.js
  3. 1 1
      packages/ckeditor5-engine/src/treemodel/delta/splitdelta.js
  4. 1 1
      packages/ckeditor5-engine/src/treemodel/delta/weakinsertdelta.js
  5. 7 47
      packages/ckeditor5-engine/src/treemodel/node.js
  6. 4 4
      packages/ckeditor5-engine/src/treemodel/nodelist.js
  7. 11 8
      packages/ckeditor5-engine/src/treemodel/operation/attributeoperation.js
  8. 5 47
      packages/ckeditor5-engine/src/treemodel/selection.js
  9. 2 2
      packages/ckeditor5-engine/src/treemodel/text.js
  10. 123 82
      packages/ckeditor5-engine/tests/treemodel/attributelist.js
  11. 3 6
      packages/ckeditor5-engine/tests/treemodel/character.js
  12. 8 8
      packages/ckeditor5-engine/tests/treemodel/delta/attributedelta.js
  13. 2 5
      packages/ckeditor5-engine/tests/treemodel/delta/mergedelta.js
  14. 8 11
      packages/ckeditor5-engine/tests/treemodel/delta/splitdelta.js
  15. 4 4
      packages/ckeditor5-engine/tests/treemodel/delta/weakinsertdelta.js
  16. 3 6
      packages/ckeditor5-engine/tests/treemodel/element.js
  17. 20 119
      packages/ckeditor5-engine/tests/treemodel/node.js
  18. 3 3
      packages/ckeditor5-engine/tests/treemodel/nodelist.js
  19. 37 40
      packages/ckeditor5-engine/tests/treemodel/operation/attributeoperation.js
  20. 1 4
      packages/ckeditor5-engine/tests/treemodel/rootelement.js
  21. 3 103
      packages/ckeditor5-engine/tests/treemodel/selection.js
  22. 3 2
      packages/ckeditor5-engine/tests/treemodel/text.js

+ 93 - 56
packages/ckeditor5-engine/src/treemodel/attributelist.js

@@ -8,111 +8,148 @@
 import Attribute from './attribute.js';
 
 /**
- * List of attributes. Used to manage a set of attributes added to and removed from an object containing
- * AttributeList.
+ * List of attributes.
  *
  * @class treeModel.AttributeList
  */
-export default class AttributeList {
+export default class AttributeList extends Map {
 	/**
-	 * Creates a list of attributes.
+	 * Creates AttributeList. If parameter is passed, initializes created list with passed {@link treeModel.Attribute}s.
 	 *
-	 * @param {Iterable.<treeModel.Attribute>} [attrs] Attributes to initialize this list with.
 	 * @constructor
+	 * @param {Iterable.<treeModel.Attribute>} attrs Attributes to initialize with.
 	 */
 	constructor( attrs ) {
+		super();
+
+		if ( attrs ) {
+			this.setTo( attrs );
+		}
+
 		/**
-		 * Internal set containing the attributes stored by this list.
+		 * Amount of attributes added to the AttributeList.
 		 *
-		 * @private
-		 * @property {Set.<treeModel.Attribute>} _attrs
+		 * @property {Number} size
 		 */
+	}
+
+	/**
+	 * AttributeList iterator. Iterates over all attributes from the list.
+	 */
+	[ Symbol.iterator ]() {
+		let it = super[ Symbol.iterator ]();
 
-		this.setAttrsTo( attrs );
+		return {
+			next: () => {
+				let step = it.next();
+
+				return {
+					value: step.value ? step.value[ 1 ] : undefined,
+					done: step.done
+				};
+			}
+		};
 	}
 
 	/**
-	 * Returns value of an attribute with given key or null if there are no attributes with given key.
+	 * Adds attribute to the attributes list. If attribute with the same key already is set, it overwrites its values.
 	 *
-	 * @param {String} key The attribute key.
-	 * @returns {*|null} Value of found attribute or null if attribute with given key has not been found.
+	 * @chainable
+	 * @param {treeModel.Attribute} attr Attribute to add or overwrite.
+	 * @returns {treeModel.AttributeList} This AttributeList object.
 	 */
-	getAttr( key ) {
-		for ( let attr of this._attrs ) {
-			if ( attr.key == key ) {
-				return attr.value;
-			}
-		}
+	set( attr ) {
+		super.set( attr.key, attr );
 
-		return null;
+		return this;
 	}
 
 	/**
-	 * Returns attribute iterator.
+	 * Removes all attributes from AttributeList and adds given attributes.
 	 *
-	 * @returns {Iterable.<treeModel.Attribute>} Attribute iterator.
+	 * @param {Iterable.<Attribute>} attrs Iterable object containing attributes to be set.
 	 */
-	getAttrs() {
-		return this._attrs[ Symbol.iterator ]();
+	setTo( attrs ) {
+		this.clear();
+
+		for ( let value of attrs ) {
+			this.set( value );
+		}
 	}
 
 	/**
-	 * Returns `true` if the object contains given {@link treeModel.Attribute attribute} or
-	 * an attribute with the same key if passed parameter was a string.
+	 * 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 An attribute or a key to look for.
-	 * @returns {Boolean} True if object contains given attribute or an attribute with the 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 exists in AttributeList. `false` otherwise.
 	 */
-	hasAttr( attrOrKey ) {
+	has( attrOrKey ) {
 		if ( attrOrKey instanceof Attribute ) {
-			for ( let attr of this._attrs ) {
-				if ( attr.isEqual( attrOrKey ) ) {
-					return true;
-				}
+			let attr = this.get( attrOrKey.key );
+
+			if ( attr ) {
+				return attr.isEqual( attrOrKey );
 			}
 		} else {
-			for ( let attr of this._attrs ) {
-				if ( attr.key == attrOrKey ) {
-					return true;
-				}
-			}
+			return super.has( attrOrKey );
 		}
 
 		return false;
 	}
 
 	/**
-	 * Removes attribute from the list of attributes.
+	 * 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 {String} key The attribute key.
+	 * @param {treeModel.AttributeList} attrs AttributeList to compare with.
+	 * @returns {Boolean} `true` if AttributeLists are equal, `false` otherwise.
 	 */
-	removeAttr( key ) {
-		for ( let attr of this._attrs ) {
-			if ( attr.key == key ) {
-				this._attrs.delete( attr );
+	isEqual( attrs ) {
+		if ( this.size != attrs.size ) {
+			return false;
+		}
 
-				return;
+		for ( let attr of attrs ) {
+			if ( !this.has( attr ) ) {
+				return false;
 			}
 		}
+
+		return true;
 	}
 
 	/**
-	 * Sets a given attribute. If the attribute with the same key already exists it will be removed.
+	 * Gets an attribute by its key.
 	 *
-	 * @param {treeModel.Attribute} attr Attribute to set.
+	 * @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.
 	 */
-	setAttr( attr ) {
-		this.removeAttr( attr.key );
 
-		this._attrs.add( attr );
-	}
+	/**
+	 * 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 and sets passed attributes.
+	 * Removes all attributes from AttributeList.
 	 *
-	 * @param {Iterable.<treeModel.Attribute>} attrs Array of attributes to set.
+	 * @method clear
 	 */
-	setAttrsTo( attrs ) {
-		this._attrs = new Set( attrs );
-	}
-}
+}

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

@@ -67,7 +67,7 @@ function attribute( batch, key, value, nodeOrRange ) {
 }
 
 function changeNode( doc, delta, key, value, node ) {
-	const previousValue = node.getAttr( key );
+	const previousValue = node.attrs.getValue( key );
 	let range;
 
 	if ( previousValue != value ) {
@@ -116,7 +116,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_LEAVE position.
 		if ( next.value.type != PositionIterator.ELEMENT_LEAVE ) {
-			valueAfter = next.value.node.getAttr( key );
+			valueAfter = next.value.node.attrs.getValue( 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 ).
@@ -153,4 +153,4 @@ function changeRange( doc, delta, key, value, range ) {
 		doc.applyOperation( operation );
 		delta.addOperation( operation );
 	}
-}
+}

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

@@ -45,7 +45,7 @@ register( 'split', function( position ) {
 		throw new CKEditorError( 'batch-split-root: Root element can not be split.' );
 	}
 
-	const copy = new Element( splitElement.name, splitElement.getAttrs() );
+	const copy = new Element( splitElement.name, splitElement.attrs );
 	const insert = new InsertOperation( Position.createAfter( splitElement ), copy, this.doc.version );
 
 	this.doc.applyOperation( insert );

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

@@ -42,7 +42,7 @@ register( 'weakInsert', function( position, nodes ) {
 	nodes = new NodeList( nodes );
 
 	for ( let node of nodes ) {
-		node.setAttrsTo( this.doc.selection.getAttrs() );
+		node.attrs.setTo( this.doc.selection.attrs );
 	}
 
 	const operation = new InsertOperation( position, nodes, this.doc.version );

+ 7 - 47
packages/ckeditor5-engine/src/treemodel/node.js

@@ -35,12 +35,14 @@ export default class Node {
 
 		/**
 		 * List of attributes set on this node.
-		 * Attributes of nodes attached to the document can be changed only be the {@link treeModel.operation.AttributeOperation}.
+		 * **Note:** It is **important** that attributes of nodes already attached to the document must be changed
+		 * only by an {@link treeModel.operation.AttributeOperation}. Do not set attributes of such nodes
+		 * using {@link treeModel.AttributeList} API.
 		 *
-		 * @private
-		 * @property {treeModel.AttributeList} _attrs
+		 * @readonly
+		 * @property {treeModel.AttributeList} attrs
 		 */
-		this._attrs = new AttributeList( attrs );
+		this.attrs = new AttributeList( attrs );
 	}
 
 	/**
@@ -102,20 +104,6 @@ export default class Node {
 		return root;
 	}
 
-	/**
-	 * @see {@link treeModel.AttributeList#getAttr}
-	 */
-	getAttr( key ) {
-		return this._attrs.getAttr( key );
-	}
-
-	/**
-	 * @see {@link treeModel.AttributeList#getAttrs}
-	 */
-	getAttrs() {
-		return this._attrs.getAttrs();
-	}
-
 	/**
 	 * Index of the node in the parent element or null if the node has no parent.
 	 *
@@ -161,34 +149,6 @@ export default class Node {
 		return path;
 	}
 
-	/**
-	 * @see {@link treeModel.AttributeList#hasAttr}
-	 */
-	hasAttr( key ) {
-		return this._attrs.hasAttr( key );
-	}
-
-	/**
-	 * @see {@link treeModel.AttributeList#removeAttr}
-	 */
-	removeAttr( key ) {
-		this._attrs.removeAttr( key );
-	}
-
-	/**
-	 * @see {@link treeModel.AttributeList#setAttr}
-	 */
-	setAttr( attr ) {
-		this._attrs.setAttr( attr );
-	}
-
-	/**
-	 * @see {@link treeModel.AttributeList#setAttrsTo}
-	 */
-	setAttrsTo( attrs ) {
-		this._attrs.setAttrsTo( attrs );
-	}
-
 	/**
 	 * Custom toJSON method to solve child-parent circular dependencies.
 	 *
@@ -202,4 +162,4 @@ export default class Node {
 
 		return json;
 	}
-}
+}

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

@@ -38,9 +38,9 @@ export default class NodeList {
 	 *
 	 *		let nodeList = new NodeList( new Text( 'foo', [ new Attribute( 'bar', 'bom' ) ] ) );
 	 *		nodeList.length; // 3
-	 *		nodeList.get( 0 ).getAttr( 'bar' ); // 'bom'
-	 *		nodeList.get( 1 ).getAttr( 'bar' ); // 'bom'
-	 *		nodeList.get( 2 ).getAttr( 'bar' ); // 'bom'
+	 *		nodeList.get( 0 ).attrs.get( 'bar' ); // 'bom'
+	 *		nodeList.get( 1 ).attrs.get( 'bar' ); // 'bom'
+	 *		nodeList.get( 2 ).attrs.get( 'bar' ); // 'bom'
 	 *
 	 *		let nodeListA = new NodeList( 'foo' );
 	 *		let nodeListB = new NodeList( nodeListA );
@@ -149,4 +149,4 @@ export default class NodeList {
 	remove( index, number ) {
 		return new NodeList( this._nodes.splice( index, number ) );
 	}
-}
+}

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

@@ -87,13 +87,14 @@ export default class AttributeOperation extends Operation {
 			 */
 			throw new CKEditorError(
 				'operation-attribute-different-keys: Old and new attributes should have the same keys.',
-				{ oldAttr: oldAttr, newAttr: newAttr } );
+				{ oldAttr: oldAttr, newAttr: newAttr }
+			);
 		}
 
 		// Remove or change.
 		if ( oldAttr !== null ) {
 			for ( let node of this.range.getAllNodes() ) {
-				if ( !node.hasAttr( oldAttr ) ) {
+				if ( !node.attrs.has( oldAttr ) ) {
 					/**
 					 * The attribute which should be removed does not exists for the given node.
 					 *
@@ -103,13 +104,14 @@ export default class AttributeOperation extends Operation {
 					 */
 					throw new CKEditorError(
 						'operation-attribute-no-attr-to-remove: The attribute which should be removed does not exists for given node.',
-						{ node: node, attr: oldAttr } );
+						{ node: node, attr: oldAttr }
+					);
 				}
 
 				// There is no use in removing attribute if we will overwrite it later.
 				// Still it is profitable to run through the loop to check if all nodes in the range has old attribute.
 				if ( newAttr === null ) {
-					node.removeAttr( oldAttr.key );
+					node.attrs.delete( oldAttr.key );
 				}
 			}
 		}
@@ -117,7 +119,7 @@ export default class AttributeOperation extends Operation {
 		// Insert or change.
 		if ( newAttr !== null ) {
 			for ( let node of this.range.getAllNodes() ) {
-				if ( oldAttr === null && node.hasAttr( newAttr.key ) ) {
+				if ( oldAttr === null && node.attrs.has( newAttr.key ) ) {
 					/**
 					 * The attribute with given key already exists for the given node.
 					 *
@@ -127,13 +129,14 @@ export default class AttributeOperation extends Operation {
 					 */
 					throw new CKEditorError(
 						'operation-attribute-attr-exists: The attribute with given key already exists.',
-						{ node: node, attr: newAttr } );
+						{ node: node, attr: newAttr }
+					);
 				}
 
-				node.setAttr( newAttr );
+				node.attrs.set( newAttr );
 			}
 		}
 
 		return { range: this.range, oldAttr: oldAttr, newAttr: newAttr };
 	}
-}
+}

+ 5 - 47
packages/ckeditor5-engine/src/treemodel/selection.js

@@ -27,10 +27,10 @@ export default class Selection {
 		/**
 		 * List of attributes set on current selection.
 		 *
-		 * @private
-		 * @property {treeModel.AttributeList} _attrs
+		 * @readonly
+		 * @property {treeModel.AttributeList} attrs
 		 */
-		this._attrs = new AttributeList();
+		this.attrs = new AttributeList();
 
 		/**
 		 * Stores all ranges that are selected.
@@ -119,7 +119,7 @@ export default class Selection {
 	}
 
 	/**
-	 * Unbinds all events previously bound by this selection and objects created by this selection.
+	 * Unbinds all events previously bound by this selection or objects created by this selection.
 	 */
 	detach() {
 		for ( let i = 0; i < this._ranges.length; i++ ) {
@@ -127,20 +127,6 @@ export default class Selection {
 		}
 	}
 
-	/**
-	 * @see {@link treeModel.AttributeList#getAttr}
-	 */
-	getAttr( key ) {
-		return this._attrs.getAttr( key );
-	}
-
-	/**
-	 * @see {@link treeModel.AttributeList#getAttrs}
-	 */
-	getAttrs() {
-		return this._attrs.getAttrs();
-	}
-
 	/**
 	 * Returns an array of ranges added to the selection. The method returns a copy of internal array, so
 	 * it will not change when ranges get added or removed from selection.
@@ -151,20 +137,6 @@ export default class Selection {
 		return this._ranges.slice();
 	}
 
-	/**
-	 * @see {@link treeModel.AttributeList#hasAttr}
-	 */
-	hasAttr( key ) {
-		return this._attrs.hasAttr( key );
-	}
-
-	/**
-	 * @see {@link treeModel.AttributeList#removeAttr}
-	 */
-	removeAttr( key ) {
-		this._attrs.removeAttr( key );
-	}
-
 	/**
 	 * Removes all ranges that were added to the selection. Fires update event.
 	 */
@@ -175,20 +147,6 @@ export default class Selection {
 		this.fire( 'update' );
 	}
 
-	/**
-	 * @see {@link treeModel.AttributeList#setAttr}
-	 */
-	setAttr( attr ) {
-		this._attrs.setAttr( attr );
-	}
-
-	/**
-	 * @see {@link treeModel.AttributeList#setAttrsTo}
-	 */
-	setAttrsTo( attrs ) {
-		this._attrs.setAttrsTo( attrs );
-	}
-
 	/**
 	 * Replaces all ranges that were added to the selection with given array of ranges. Last range of the array
 	 * is treated like the last added range and is used to set {@link #anchor} and {@link #focus}. Accepts a flag
@@ -241,4 +199,4 @@ function pushRange( range ) {
 	this._ranges.push( LiveRange.createFromRange( range ) );
 }
 
-objectUtils.extend( Selection.prototype, EmitterMixin );
+objectUtils.extend( Selection.prototype, EmitterMixin );

+ 2 - 2
packages/ckeditor5-engine/src/treemodel/text.js

@@ -33,6 +33,6 @@ export default class Text {
 		 *
 		 * @property {Iterable}
 		 */
-		this.attrs = attrs;
+		this.attrs = new AttributeList( attrs );
 	}
-}
+}

+ 123 - 82
packages/ckeditor5-engine/tests/treemodel/attributelist.js

@@ -7,12 +7,9 @@
 
 'use strict';
 
-import coreTestUtils from '/tests/core/_utils/utils.js';
 import AttributeList from '/ckeditor5/core/treemodel/attributelist.js';
 import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 
-const getIteratorCount = coreTestUtils.getIteratorCount;
-
 describe( 'AttributeList', () => {
 	let list, attrFooBar;
 
@@ -21,129 +18,173 @@ describe( 'AttributeList', () => {
 		attrFooBar = new Attribute( 'foo', 'bar' );
 	} );
 
-	describe( 'setAttr', () => {
-		it( 'should insert an attribute', () => {
-			list.setAttr( attrFooBar );
+	it( 'should extend Map', () => {
+		expect( list ).to.be.instanceof( Map );
+	} );
 
-			expect( getIteratorCount( list.getAttrs() ) ).to.equal( 1 );
-			expect( list.getAttr( attrFooBar.key ) ).to.equal( attrFooBar.value );
+	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 overwrite attribute with the same key', () => {
-			list.setAttr( attrFooBar );
+		it( 'should copy passed AttributeList', () => {
+			list = new AttributeList( [ attrFooBar ] );
+			let copy = new AttributeList( list );
 
-			expect( getIteratorCount( list.getAttrs() ) ).to.equal( 1 );
-			expect( list.getAttr( 'foo' ) ).to.equal( 'bar' );
+			expect( copy.size ).to.equal( 1 );
+			expect( copy.has( 'foo' ) ).to.be.true;
+			expect( copy.get( 'foo' ).value ).to.equal( 'bar' );
+		} );
+	} );
 
-			let attrFooXyz = new Attribute( 'foo', 'xyz' );
+	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();
 
-			list.setAttr( attrFooXyz );
+			expect( step.done ).to.be.false;
+			expect( step.value ).to.equal( attrFooBar );
 
-			expect( getIteratorCount( list.getAttrs() ) ).to.equal( 1 );
-			expect( list.getAttr( 'foo' ) ).to.equal( 'xyz' );
+			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( 'setAttrsTo', () => {
-		it( 'should remove all attributes and set passed ones', () => {
-			list.setAttr( attrFooBar );
+	describe( 'getValue', () => {
+		it( 'should return value of set attribute for given key', () => {
+			list.set( attrFooBar );
+			expect( list.getValue( 'foo' ) ).to.equal( 'bar' );
+		} );
 
-			let attrs = [ new Attribute( 'abc', true ), new Attribute( 'xyz', false ) ];
+		it( 'should return null if attribute with given key is not set', () => {
+			expect( list.getValue( 'foo' ) ).to.be.null;
+		} );
+	} );
 
-			list.setAttrsTo( attrs );
+	describe( 'set', () => {
+		it( 'should insert given attribute', () => {
+			list.set( attrFooBar );
 
-			expect( getIteratorCount( list.getAttrs() ) ).to.equal( 2 );
-			expect( list.getAttr( 'foo' ) ).to.be.null;
-			expect( list.getAttr( 'abc' ) ).to.be.true;
-			expect( list.getAttr( 'xyz' ) ).to.be.false;
+			expect( list.size ).to.equal( 1 );
+			expect( list.getValue( 'foo' ) ).to.equal( 'bar' );
 		} );
 
-		it( 'should copy attributes array, not pass by reference', () => {
-			let attrs = [ new Attribute( 'attr', true ) ];
+		it( 'should overwrite attribute with the same key', () => {
+			list.set( attrFooBar );
+
+			expect( list.size ).to.equal( 1 );
+			expect( list.getValue( 'foo' ) ).to.equal( 'bar' );
 
-			list.setAttrsTo( attrs );
+			let attrFooXyz = new Attribute( 'foo', 'xyz' );
 
-			attrs.pop();
+			list.set( attrFooXyz );
 
-			expect( getIteratorCount( list.getAttrs() ) ).to.equal( 1 );
+			expect( list.size ).to.equal( 1 );
+			expect( list.getValue( 'foo' ) ).to.equal( 'xyz' );
 		} );
 	} );
 
-	describe( 'getAttr', () => {
-		beforeEach( () => {
-			list.setAttr( attrFooBar );
-		} );
+	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 ) ] );
 
-		it( 'should return attribute value if key of previously set attribute has been passed', () => {
-			expect( list.getAttr( 'foo' ) ).to.equal( attrFooBar.value );
+			expect( list.has( 'foo' ) ).to.be.false;
+			expect( list.getValue( 'abc' ) ).to.be.true;
+			expect( list.getValue( 'bar' ) ).to.be.false;
 		} );
+	} );
 
-		it( 'should return null if attribute with given key has not been found', () => {
-			expect( list.getAttr( 'bar' ) ).to.be.null;
+	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;
 		} );
-	} );
 
-	describe( 'removeAttr', () => {
-		it( 'should remove an attribute', () => {
-			let attrA = new Attribute( 'a', 'A' );
-			let attrB = new Attribute( 'b', 'B' );
-			let attrC = new Attribute( 'c', 'C' );
+		it( 'should return true if list contains an attribute with given key', () => {
+			list.set( attrFooBar );
 
-			list.setAttr( attrA );
-			list.setAttr( attrB );
-			list.setAttr( attrC );
+			expect( list.has( 'foo' ) ).to.be.true;
+		} );
 
-			list.removeAttr( attrB.key );
+		it( 'should return false if list does not contain given attribute', () => {
+			list.set( attrFooBar );
 
-			expect( getIteratorCount( list.getAttrs() ) ).to.equal( 2 );
-			expect( list.getAttr( attrA.key ) ).to.equal( attrA.value );
-			expect( list.getAttr( attrC.key ) ).to.equal( attrC.value );
-			expect( list.getAttr( attrB.key ) ).to.be.null;
+			expect( list.has( new Attribute( 'abc', true ) ) ).to.be.false;
 		} );
-	} );
 
-	describe( 'hasAttr', () => {
-		it( 'should check attribute by key', () => {
-			list.setAttr( attrFooBar );
-			expect( list.hasAttr( 'foo' ) ).to.be.true;
+		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 attribute was not found by key', () => {
-			expect( list.hasAttr( 'bar' ) ).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 ] );
 
-		it( 'should check attribute by object', () => {
-			list.setAttr( attrFooBar );
-			expect( list.hasAttr( attrFooBar ) ).to.be.true;
+			let other = new AttributeList( [ attrFooBar ] );
+
+			expect( list.isEqual( other ) ).to.be.false;
+			expect( other.isEqual( list ) ).to.be.false;
 		} );
 
-		it( 'should return false if attribute was not found by object', () => {
-			expect( list.hasAttr( attrFooBar ) ).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;
 		} );
-	} );
 
-	describe( 'getAttrs', () => {
-		it( 'should return all set attributes', () => {
-			let attrA = new Attribute( 'a', 'A' );
-			let attrB = new Attribute( 'b', 'B' );
-			let attrC = new Attribute( 'c', 'C' );
+		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.setAttrsTo( [
-				attrA,
-				attrB,
-				attrC
-			] );
+			list.setTo( [ attrFooBar, attrAbcXyz ] );
 
-			list.removeAttr( attrB.key );
+			let other = new AttributeList( [ attrFooTrue, attrAbcXyz ] );
+
+			expect( list.isEqual( other ) ).to.be.false;
+			expect( other.isEqual( list ) ).to.be.false;
+		} );
 
-			let attrsIt = list.getAttrs();
-			let attrs = [];
+		it( 'should return true if lists have same attributes and same values for them', () => {
+			let attrAbcXyz = new Attribute( 'abc', 'xyz' );
+			list.setTo( [ attrFooBar, attrAbcXyz ] );
 
-			for ( let attr of attrsIt ) {
-				attrs.push( attr );
-			}
+			// Note different order of attributes.
+			let other = new AttributeList( [ attrAbcXyz, attrFooBar ] );
 
-			expect( [ attrA, attrC ] ).to.deep.equal( attrs );
+			expect( list.isEqual( other ) ).to.be.true;
+			expect( other.isEqual( list ) ).to.be.true;
 		} );
 	} );
 } );

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

@@ -7,14 +7,11 @@
 
 'use strict';
 
-import coreTestUtils from '/tests/core/_utils/utils.js';
 import Character from '/ckeditor5/core/treemodel/character.js';
 import Node from '/ckeditor5/core/treemodel/node.js';
 import Element from '/ckeditor5/core/treemodel/element.js';
 import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 
-const getIteratorCount = coreTestUtils.getIteratorCount;
-
 describe( 'Character', () => {
 	describe( 'constructor', () => {
 		it( 'should create character without attributes', () => {
@@ -24,7 +21,7 @@ describe( 'Character', () => {
 			expect( character ).to.be.an.instanceof( Node );
 			expect( character ).to.have.property( 'character' ).that.equals( 'f' );
 			expect( character ).to.have.property( 'parent' ).that.equals( parent );
-			expect( getIteratorCount( character.getAttrs() ) ).to.equal( 0 );
+			expect( character.attrs.size ).to.equal( 0 );
 		} );
 
 		it( 'should create character with attributes', () => {
@@ -35,8 +32,8 @@ describe( 'Character', () => {
 			expect( character ).to.be.an.instanceof( Node );
 			expect( character ).to.have.property( 'character' ).that.equals( 'f' );
 			expect( character ).to.have.property( 'parent' ).that.equals( parent );
-			expect( getIteratorCount( character.getAttrs() ) ).to.equal( 1 );
-			expect( character.getAttr( attr.key ) ).to.equal( attr.value );
+			expect( character.attrs.size ).to.equal( 1 );
+			expect( character.attrs.get( attr.key ) ).to.equal( attr );
 		} );
 	} );
 } );

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

@@ -50,31 +50,31 @@ describe( 'Batch', () => {
 			it( 'should create the attribute on element', () => {
 				batch.setAttr( 'b', 2, node );
 				expect( getOperationsCount() ).to.equal( 1 );
-				expect( node.getAttr( 'b' ) ).to.equal( 2 );
+				expect( node.attrs.getValue( 'b' ) ).to.equal( 2 );
 			} );
 
 			it( 'should change the attribute of element', () => {
 				batch.setAttr( 'a', 2, node );
 				expect( getOperationsCount() ).to.equal( 1 );
-				expect( node.getAttr( 'a' ) ).to.equal( 2 );
+				expect( node.attrs.getValue( 'a' ) ).to.equal( 2 );
 			} );
 
 			it( 'should create the attribute on character', () => {
 				batch.setAttr( 'b', 2, character );
 				expect( getOperationsCount() ).to.equal( 1 );
-				expect( character.getAttr( 'b' ) ).to.equal( 2 );
+				expect( character.attrs.getValue( 'b' ) ).to.equal( 2 );
 			} );
 
 			it( 'should change the attribute of character', () => {
 				batch.setAttr( 'a', 2, character );
 				expect( getOperationsCount() ).to.equal( 1 );
-				expect( character.getAttr( 'a' ) ).to.equal( 2 );
+				expect( character.attrs.getValue( '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.getAttr( 'a' ) ).to.equal( 1 );
+				expect( node.attrs.getValue( 'a' ) ).to.equal( 1 );
 			} );
 
 			it( 'should be chainable', () => {
@@ -87,13 +87,13 @@ describe( 'Batch', () => {
 			it( 'should remove the attribute from element', () => {
 				batch.removeAttr( 'a', node );
 				expect( getOperationsCount() ).to.equal( 1 );
-				expect( node.getAttr( 'a' ) ).to.be.null;
+				expect( node.attrs.getValue( 'a' ) ).to.be.null;
 			} );
 
 			it( 'should remove the attribute from character', () => {
 				batch.removeAttr( 'a', character );
 				expect( getOperationsCount() ).to.equal( 1 );
-				expect( character.getAttr( 'a' ) ).to.be.null;
+				expect( character.attrs.getValue( 'a' ) ).to.be.null;
 			} );
 
 			it( 'should do nothing if the attribute is not set', () => {
@@ -145,7 +145,7 @@ describe( 'Batch', () => {
 			// default: 111---111222---1112------
 			const range = Range.createFromElement( root );
 
-			return Array.from( range.getAllNodes() ).map( node => node.getAttr( 'a' ) || '-' ).join( '' );
+			return Array.from( range.getAllNodes() ).map( node => node.attrs.getValue( 'a' ) || '-' ).join( '' );
 		}
 
 		describe( 'setAttr', () => {

+ 2 - 5
packages/ckeditor5-engine/tests/treemodel/delta/mergedelta.js

@@ -7,15 +7,12 @@
 
 'use strict';
 
-import coreTestUtils from '/tests/core/_utils/utils.js';
 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';
 
-const getIteratorCount = coreTestUtils.getIteratorCount;
-
 describe( 'Batch', () => {
 	let doc, root, p1, p2;
 
@@ -36,8 +33,8 @@ describe( 'Batch', () => {
 			expect( root.getChildCount() ).to.equal( 1 );
 			expect( root.getChild( 0 ).name ).to.equal( 'p' );
 			expect( root.getChild( 0 ).getChildCount() ).to.equal( 6 );
-			expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 1 );
-			expect( root.getChild( 0 ).getAttr( 'key1' ) ).to.equal( 'value1' );
+			expect( root.getChild( 0 ).attrs.size ).to.equal( 1 );
+			expect( root.getChild( 0 ).attrs.getValue( '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' );

+ 8 - 11
packages/ckeditor5-engine/tests/treemodel/delta/splitdelta.js

@@ -7,15 +7,12 @@
 
 'use strict';
 
-import coreTestUtils from '/tests/core/_utils/utils.js';
 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';
 
-const getIteratorCount = coreTestUtils.getIteratorCount;
-
 describe( 'Batch', () => {
 	let doc, root, p;
 
@@ -36,16 +33,16 @@ describe( 'Batch', () => {
 
 			expect( root.getChild( 0 ).name ).to.equal( 'p' );
 			expect( root.getChild( 0 ).getChildCount() ).to.equal( 3 );
-			expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 1 );
-			expect( root.getChild( 0 ).getAttr( 'key' ) ).to.equal( 'value' );
+			expect( root.getChild( 0 ).attrs.size ).to.equal( 1 );
+			expect( root.getChild( 0 ).attrs.getValue( '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' );
 
 			expect( root.getChild( 1 ).name ).to.equal( 'p' );
 			expect( root.getChild( 1 ).getChildCount() ).to.equal( 3 );
-			expect( getIteratorCount( root.getChild( 1 ).getAttrs() ) ).to.equal( 1 );
-			expect( root.getChild( 1 ).getAttr( 'key' ) ).to.equal( 'value' );
+			expect( root.getChild( 1 ).attrs.size ).to.equal( 1 );
+			expect( root.getChild( 1 ).attrs.getValue( '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' );
@@ -58,8 +55,8 @@ describe( 'Batch', () => {
 
 			expect( root.getChild( 0 ).name ).to.equal( 'p' );
 			expect( root.getChild( 0 ).getChildCount() ).to.equal( 6 );
-			expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 1 );
-			expect( root.getChild( 0 ).getAttr( 'key' ) ).to.equal( 'value' );
+			expect( root.getChild( 0 ).attrs.size ).to.equal( 1 );
+			expect( root.getChild( 0 ).attrs.getValue( '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' );
@@ -69,8 +66,8 @@ describe( 'Batch', () => {
 
 			expect( root.getChild( 1 ).name ).to.equal( 'p' );
 			expect( root.getChild( 1 ).getChildCount() ).to.equal( 0 );
-			expect( getIteratorCount( root.getChild( 1 ).getAttrs() ) ).to.equal( 1 );
-			expect( root.getChild( 1 ).getAttr( 'key' ) ).to.equal( 'value' );
+			expect( root.getChild( 1 ).attrs.size ).to.equal( 1 );
+			expect( root.getChild( 1 ).attrs.getValue( 'key' ) ).to.equal( 'value' );
 		} );
 
 		it( 'should throw if we try to split a root', () => {

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

@@ -27,7 +27,7 @@ describe( 'Batch', () => {
 			new Attribute( 'foo', 'bar' )
 		];
 
-		doc.selection.setAttrsTo( attrs );
+		doc.selection.attrs.setTo( attrs );
 
 		chain = batch.weakInsert( new Position( root, [ 2 ] ), 'xyz' );
 	} );
@@ -41,9 +41,9 @@ describe( 'Batch', () => {
 		} );
 
 		it( 'should set inserted nodes attributes to same as current selection attributes', () => {
-			expect( Array.from( root.getChild( 2 ).getAttrs() ) ).to.deep.equal( attrs );
-			expect( Array.from( root.getChild( 3 ).getAttrs() ) ).to.deep.equal( attrs );
-			expect( Array.from( root.getChild( 4 ).getAttrs() ) ).to.deep.equal( attrs );
+			expect( Array.from( root.getChild( 2 ).attrs ) ).to.deep.equal( attrs );
+			expect( Array.from( root.getChild( 3 ).attrs ) ).to.deep.equal( attrs );
+			expect( Array.from( root.getChild( 4 ).attrs ) ).to.deep.equal( attrs );
 		} );
 
 		it( 'should be chainable', () => {

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

@@ -7,14 +7,11 @@
 
 'use strict';
 
-import coreTestUtils from '/tests/core/_utils/utils.js';
 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';
 
-const getIteratorCount = coreTestUtils.getIteratorCount;
-
 describe( 'Element', () => {
 	describe( 'constructor', () => {
 		it( 'should create element without attributes', () => {
@@ -24,7 +21,7 @@ describe( 'Element', () => {
 			expect( element ).to.be.an.instanceof( Node );
 			expect( element ).to.have.property( 'name' ).that.equals( 'elem' );
 			expect( element ).to.have.property( 'parent' ).that.equals( parent );
-			expect( getIteratorCount( element.getAttrs() ) ).to.equal( 0 );
+			expect( element.attrs.size ).to.equal( 0 );
 		} );
 
 		it( 'should create element with attributes', () => {
@@ -36,8 +33,8 @@ describe( 'Element', () => {
 			expect( element ).to.be.an.instanceof( Node );
 			expect( element ).to.have.property( 'name' ).that.equals( 'elem' );
 			expect( element ).to.have.property( 'parent' ).that.equals( parent );
-			expect( getIteratorCount( element.getAttrs() ) ).to.equal( 1 );
-			expect( element.getAttr( attr.key ) ).to.equal( attr.value );
+			expect( element.attrs.size ).to.equal( 1 );
+			expect( element.attrs.getValue( attr.key ) ).to.equal( attr.value );
 		} );
 
 		it( 'should create element with children', () => {

+ 20 - 119
packages/ckeditor5-engine/tests/treemodel/node.js

@@ -7,19 +7,17 @@
 
 'use strict';
 
-import coreTestUtils from '/tests/core/_utils/utils.js';
-import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 import Element from '/ckeditor5/core/treemodel/element.js';
-import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 import Character from '/ckeditor5/core/treemodel/character.js';
-
-const getIteratorCount = coreTestUtils.getIteratorCount;
+import Attribute from '/ckeditor5/core/treemodel/attribute.js';
+import AttributeList from '/ckeditor5/core/treemodel/attributelist.js';
+import NodeList from '/ckeditor5/core/treemodel/nodelist.js';
+import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
 describe( 'Node', () => {
 	let root;
 	let one, two, three;
-	let charB, charA, charR, img, attrEle;
-	let attrFooBar;
+	let charB, charA, charR, img;
 
 	before( () => {
 		charB = new Character( 'b' );
@@ -32,12 +30,6 @@ describe( 'Node', () => {
 		three = new Element( 'three' );
 
 		root = new Element( null, null, [ one, two, three ] );
-
-		attrFooBar = new Attribute( 'foo', 'bar' );
-	} );
-
-	beforeEach( () => {
-		attrEle = new Element( 'element' );
 	} );
 
 	describe( 'should have a correct property', () => {
@@ -95,15 +87,23 @@ describe( 'Node', () => {
 	} );
 
 	describe( 'constructor', () => {
-		it( 'should copy attributes list, not pass by reference', () => {
-			let attrs = [ new Attribute( 'attr', true ) ];
-			let foo = new Element( 'foo', attrs );
-			let bar = new Element( 'bar', attrs );
+		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.size ).to.equal( 0 );
+		} );
 
-			foo.removeAttr( 'attr' );
+		it( 'should initialize attribute list with passed attributes', () => {
+			let attrs = [
+				new Attribute( 'foo', true ),
+				new Attribute( 'bar', false )
+			];
+			let foo = new Element( 'foo', attrs );
 
-			expect( getIteratorCount( foo.getAttrs() ) ).to.equal( 0 );
-			expect( getIteratorCount( bar.getAttrs() ) ).to.equal( 1 );
+			expect( foo.attrs.size ).to.equal( 2 );
+			expect( foo.attrs.getValue( 'foo' ) ).to.equal( true );
+			expect( foo.attrs.getValue( 'bar' ) ).to.equal( false );
 		} );
 	} );
 
@@ -162,103 +162,4 @@ describe( 'Node', () => {
 			expect( charR.getPath() ).to.deep.equal( [ 1, 3 ] );
 		} );
 	} );
-
-	// Testing integration with attributes list.
-	// Tests copied from AttributeList tests.
-	// Some cases were omitted.
-
-	describe( 'setAttr', () => {
-		it( 'should insert an attribute', () => {
-			attrEle.setAttr( attrFooBar );
-
-			expect( getIteratorCount( attrEle.getAttrs() ) ).to.equal( 1 );
-			expect( attrEle.getAttr( attrFooBar.key ) ).to.equal( attrFooBar.value );
-		} );
-	} );
-
-	describe( 'setAttrsTo', () => {
-		it( 'should remove all attributes and set passed ones', () => {
-			attrEle.setAttr( attrFooBar );
-
-			let attrs = [ new Attribute( 'abc', true ), new Attribute( 'xyz', false ) ];
-
-			attrEle.setAttrsTo( attrs );
-
-			expect( getIteratorCount( attrEle.getAttrs() ) ).to.equal( 2 );
-			expect( attrEle.getAttr( 'foo' ) ).to.be.null;
-			expect( attrEle.getAttr( 'abc' ) ).to.be.true;
-			expect( attrEle.getAttr( 'xyz' ) ).to.be.false;
-		} );
-	} );
-
-	describe( 'getAttr', () => {
-		beforeEach( () => {
-			attrEle = new Element( 'e', [ attrFooBar ] );
-		} );
-
-		it( 'should return attribute value if key of previously set attribute has been passed', () => {
-			expect( attrEle.getAttr( 'foo' ) ).to.equal( attrFooBar.value );
-		} );
-
-		it( 'should return null if attribute with given key has not been found', () => {
-			expect( attrEle.getAttr( 'bar' ) ).to.be.null;
-		} );
-	} );
-
-	describe( 'removeAttr', () => {
-		it( 'should remove an attribute', () => {
-			let attrA = new Attribute( 'a', 'A' );
-			let attrB = new Attribute( 'b', 'B' );
-			let attrC = new Attribute( 'c', 'C' );
-
-			attrEle.setAttr( attrA );
-			attrEle.setAttr( attrB );
-			attrEle.setAttr( attrC );
-
-			attrEle.removeAttr( attrB.key );
-
-			expect( getIteratorCount( attrEle.getAttrs() ) ).to.equal( 2 );
-			expect( attrEle.getAttr( attrA.key ) ).to.equal( attrA.value );
-			expect( attrEle.getAttr( attrC.key ) ).to.equal( attrC.value );
-			expect( attrEle.getAttr( attrB.key ) ).to.be.null;
-		} );
-	} );
-
-	describe( 'hasAttr', () => {
-		it( 'should check attribute by key', () => {
-			attrEle.setAttr( attrFooBar );
-			expect( attrEle.hasAttr( 'foo' ) ).to.be.true;
-		} );
-
-		it( 'should return false if attribute was not found by key', () => {
-			expect( attrEle.hasAttr( 'bar' ) ).to.be.false;
-		} );
-
-		it( 'should check attribute by object', () => {
-			attrEle.setAttr( attrFooBar );
-			expect( attrEle.hasAttr( attrFooBar ) ).to.be.true;
-		} );
-
-		it( 'should return false if attribute was not found by object', () => {
-			expect( attrEle.hasAttr( attrFooBar ) ).to.be.false;
-		} );
-	} );
-
-	describe( 'getAttrs', () => {
-		it( 'should return all set attributes', () => {
-			let attrA = new Attribute( 'a', 'A' );
-			let attrB = new Attribute( 'b', 'B' );
-			let attrC = new Attribute( 'c', 'C' );
-
-			attrEle.setAttrsTo( [
-				attrA,
-				attrB,
-				attrC
-			] );
-
-			attrEle.removeAttr( attrB.key );
-
-			expect( [ attrA, attrC ] ).to.deep.equal( Array.from( attrEle.getAttrs() ) );
-		} );
-	} );
 } );

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

@@ -49,11 +49,11 @@ describe( 'NodeList', () => {
 
 			expect( nodeList.length ).to.equal( 3 );
 			expect( nodeList.get( 0 ).character ).to.equal( 'f' );
-			expect( nodeList.get( 0 ).getAttr( attr.key ) ).to.equal( attr.value );
+			expect( nodeList.get( 0 ).attrs.getValue( attr.key ) ).to.equal( attr.value );
 			expect( nodeList.get( 1 ).character ).to.equal( 'o' );
-			expect( nodeList.get( 1 ).getAttr( attr.key ) ).to.equal( attr.value );
+			expect( nodeList.get( 1 ).attrs.getValue( attr.key ) ).to.equal( attr.value );
 			expect( nodeList.get( 2 ).character ).to.equal( 'o' );
-			expect( nodeList.get( 2 ).getAttr( attr.key ) ).to.equal( attr.value );
+			expect( nodeList.get( 2 ).attrs.getValue( attr.key ) ).to.equal( attr.value );
 		} );
 	} );
 

+ 37 - 40
packages/ckeditor5-engine/tests/treemodel/operation/attributeoperation.js

@@ -7,7 +7,6 @@
 
 'use strict';
 
-import coreTestUtils from '/tests/core/_utils/utils.js';
 import Document from '/ckeditor5/core/treemodel/document.js';
 import Element from '/ckeditor5/core/treemodel/element.js';
 import AttributeOperation from '/ckeditor5/core/treemodel/operation/attributeoperation.js';
@@ -18,8 +17,6 @@ import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 import Text from '/ckeditor5/core/treemodel/text.js';
 import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
-const getIteratorCount = coreTestUtils.getIteratorCount;
-
 describe( 'AttributeOperation', () => {
 	let doc, root;
 
@@ -55,9 +52,9 @@ describe( 'AttributeOperation', () => {
 
 		expect( doc.version ).to.equal( 1 );
 		expect( root.getChildCount() ).to.equal( 3 );
-		expect( root.getChild( 0 ).hasAttr( newAttr ) ).to.be.true;
-		expect( root.getChild( 1 ).hasAttr( newAttr ) ).to.be.true;
-		expect( getIteratorCount( root.getChild( 2 ).getAttrs() ) ).to.equal( 0 );
+		expect( root.getChild( 0 ).attrs.has( newAttr ) ).to.be.true;
+		expect( root.getChild( 1 ).attrs.has( newAttr ) ).to.be.true;
+		expect( root.getChild( 2 ).attrs.size ).to.equal( 0 );
 	} );
 
 	it( 'should add attribute to the existing attributes', () => {
@@ -78,10 +75,10 @@ describe( 'AttributeOperation', () => {
 
 		expect( doc.version ).to.equal( 1 );
 		expect( root.getChildCount() ).to.equal( 1 );
-		expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 3 );
-		expect( root.getChild( 0 ).hasAttr( newAttr ) ).to.be.true;
-		expect( root.getChild( 0 ).hasAttr( fooAttr ) ).to.be.true;
-		expect( root.getChild( 0 ).hasAttr( barAttr ) ).to.be.true;
+		expect( root.getChild( 0 ).attrs.size ).to.equal( 3 );
+		expect( root.getChild( 0 ).attrs.has( newAttr ) ).to.be.true;
+		expect( root.getChild( 0 ).attrs.has( fooAttr ) ).to.be.true;
+		expect( root.getChild( 0 ).attrs.has( barAttr ) ).to.be.true;
 	} );
 
 	it( 'should change attribute to the set of nodes', () => {
@@ -101,12 +98,12 @@ describe( 'AttributeOperation', () => {
 
 		expect( doc.version ).to.equal( 1 );
 		expect( root.getChildCount() ).to.equal( 3 );
-		expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 1 );
-		expect( root.getChild( 0 ).hasAttr( newAttr ) ).to.be.true;
-		expect( getIteratorCount( root.getChild( 1 ).getAttrs() ) ).to.equal( 1 );
-		expect( root.getChild( 1 ).hasAttr( newAttr ) ).to.be.true;
-		expect( getIteratorCount( root.getChild( 2 ).getAttrs() ) ).to.equal( 1 );
-		expect( root.getChild( 2 ).hasAttr( oldAttr ) ).to.be.true;
+		expect( root.getChild( 0 ).attrs.size ).to.equal( 1 );
+		expect( root.getChild( 0 ).attrs.has( newAttr ) ).to.be.true;
+		expect( root.getChild( 1 ).attrs.size ).to.equal( 1 );
+		expect( root.getChild( 1 ).attrs.has( newAttr ) ).to.be.true;
+		expect( root.getChild( 2 ).attrs.size ).to.equal( 1 );
+		expect( root.getChild( 2 ).attrs.has( oldAttr ) ).to.be.true;
 	} );
 
 	it( 'should change attribute in the middle of existing attributes', () => {
@@ -128,10 +125,10 @@ describe( 'AttributeOperation', () => {
 
 		expect( doc.version ).to.equal( 1 );
 		expect( root.getChildCount() ).to.equal( 1 );
-		expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 3 );
-		expect( root.getChild( 0 ).hasAttr( fooAttr ) ).to.be.true;
-		expect( root.getChild( 0 ).hasAttr( x2Attr ) ).to.be.true;
-		expect( root.getChild( 0 ).hasAttr( barAttr ) ).to.be.true;
+		expect( root.getChild( 0 ).attrs.size ).to.equal( 3 );
+		expect( root.getChild( 0 ).attrs.has( fooAttr ) ).to.be.true;
+		expect( root.getChild( 0 ).attrs.has( x2Attr ) ).to.be.true;
+		expect( root.getChild( 0 ).attrs.has( barAttr ) ).to.be.true;
 	} );
 
 	it( 'should remove attribute', () => {
@@ -152,9 +149,9 @@ describe( 'AttributeOperation', () => {
 
 		expect( doc.version ).to.equal( 1 );
 		expect( root.getChildCount() ).to.equal( 1 );
-		expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 2 );
-		expect( root.getChild( 0 ).hasAttr( fooAttr ) ).to.be.true;
-		expect( root.getChild( 0 ).hasAttr( barAttr ) ).to.be.true;
+		expect( root.getChild( 0 ).attrs.size ).to.equal( 2 );
+		expect( root.getChild( 0 ).attrs.has( fooAttr ) ).to.be.true;
+		expect( root.getChild( 0 ).attrs.has( barAttr ) ).to.be.true;
 	} );
 
 	it( 'should create an AttributeOperation as a reverse', () => {
@@ -190,9 +187,9 @@ describe( 'AttributeOperation', () => {
 
 		expect( doc.version ).to.equal( 2 );
 		expect( root.getChildCount() ).to.equal( 3 );
-		expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 0 );
-		expect( getIteratorCount( root.getChild( 1 ).getAttrs() ) ).to.equal( 0 );
-		expect( getIteratorCount( root.getChild( 2 ).getAttrs() ) ).to.equal( 0 );
+		expect( root.getChild( 0 ).attrs.size ).to.equal( 0 );
+		expect( root.getChild( 1 ).attrs.size ).to.equal( 0 );
+		expect( root.getChild( 2 ).attrs.size ).to.equal( 0 );
 	} );
 
 	it( 'should not set attribute of element if change range starts in the middle of that element', () => {
@@ -212,7 +209,7 @@ describe( 'AttributeOperation', () => {
 			)
 		);
 
-		expect( root.getChild( 0 ).hasAttr( fooAttr ) ).to.be.false;
+		expect( root.getChild( 0 ).attrs.has( fooAttr ) ).to.be.false;
 	} );
 
 	it( 'should not remove attribute of element if change range starts in the middle of that element', () => {
@@ -232,7 +229,7 @@ describe( 'AttributeOperation', () => {
 			)
 		);
 
-		expect( root.getChild( 0 ).hasAttr( fooAttr ) ).to.be.true;
+		expect( root.getChild( 0 ).attrs.has( fooAttr ) ).to.be.true;
 	} );
 
 	it( 'should undo changing attribute by applying reverse operation', () => {
@@ -256,12 +253,12 @@ describe( 'AttributeOperation', () => {
 
 		expect( doc.version ).to.equal( 2 );
 		expect( root.getChildCount() ).to.equal( 3 );
-		expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 1 );
-		expect( root.getChild( 0 ).hasAttr( oldAttr ) ).to.be.true;
-		expect( getIteratorCount( root.getChild( 1 ).getAttrs() ) ).to.equal( 1 );
-		expect( root.getChild( 1 ).hasAttr( oldAttr ) ).to.be.true;
-		expect( getIteratorCount( root.getChild( 2 ).getAttrs() ) ).to.equal( 1 );
-		expect( root.getChild( 2 ).hasAttr( oldAttr ) ).to.be.true;
+		expect( root.getChild( 0 ).attrs.size ).to.equal( 1 );
+		expect( root.getChild( 0 ).attrs.has( oldAttr ) ).to.be.true;
+		expect( root.getChild( 1 ).attrs.size ).to.equal( 1 );
+		expect( root.getChild( 1 ).attrs.has( oldAttr ) ).to.be.true;
+		expect( root.getChild( 2 ).attrs.size ).to.equal( 1 );
+		expect( root.getChild( 2 ).attrs.has( oldAttr ) ).to.be.true;
 	} );
 
 	it( 'should undo remove attribute by applying reverse operation', () => {
@@ -284,12 +281,12 @@ describe( 'AttributeOperation', () => {
 
 		expect( doc.version ).to.equal( 2 );
 		expect( root.getChildCount() ).to.equal( 3 );
-		expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 1 );
-		expect( root.getChild( 0 ).hasAttr( fooAttr ) ).to.be.true;
-		expect( getIteratorCount( root.getChild( 1 ).getAttrs() ) ).to.equal( 1 );
-		expect( root.getChild( 1 ).hasAttr( fooAttr ) ).to.be.true;
-		expect( getIteratorCount( root.getChild( 2 ).getAttrs() ) ).to.equal( 1 );
-		expect( root.getChild( 2 ).hasAttr( fooAttr ) ).to.be.true;
+		expect( root.getChild( 0 ).attrs.size ).to.equal( 1 );
+		expect( root.getChild( 0 ).attrs.has( fooAttr ) ).to.be.true;
+		expect( root.getChild( 1 ).attrs.size ).to.equal( 1 );
+		expect( root.getChild( 1 ).attrs.has( fooAttr ) ).to.be.true;
+		expect( root.getChild( 2 ).attrs.size ).to.equal( 1 );
+		expect( root.getChild( 2 ).attrs.has( fooAttr ) ).to.be.true;
 	} );
 
 	it( 'should throw an error when one try to remove and the attribute does not exists', () => {

+ 1 - 4
packages/ckeditor5-engine/tests/treemodel/rootelement.js

@@ -7,13 +7,10 @@
 
 'use strict';
 
-import coreTestUtils from '/tests/core/_utils/utils.js';
 import Document from '/ckeditor5/core/treemodel/document.js';
 import Element from '/ckeditor5/core/treemodel/element.js';
 import RootElement from '/ckeditor5/core/treemodel/rootelement.js';
 
-const getIteratorCount = coreTestUtils.getIteratorCount;
-
 describe( 'Element', () => {
 	describe( 'constructor', () => {
 		it( 'should create root element without attributes', () => {
@@ -22,7 +19,7 @@ describe( 'Element', () => {
 
 			expect( root ).to.be.an.instanceof( Element );
 			expect( root ).to.have.property( 'document' ).that.equals( doc );
-			expect( getIteratorCount( root.getAttrs() ) ).to.equal( 0 );
+			expect( root.attrs.size ).to.equal( 0 );
 			expect( root.getChildCount() ).to.equal( 0 );
 		} );
 	} );

+ 3 - 103
packages/ckeditor5-engine/tests/treemodel/selection.js

@@ -7,7 +7,6 @@
 
 'use strict';
 
-import coreTestUtils from '/tests/core/_utils/utils.js';
 import Document from '/ckeditor5/core/treemodel/document.js';
 import Attribute from '/ckeditor5/core/treemodel/attribute.js';
 import Element from '/ckeditor5/core/treemodel/element.js';
@@ -19,8 +18,6 @@ import InsertOperation from '/ckeditor5/core/treemodel/operation/insertoperation
 import MoveOperation from '/ckeditor5/core/treemodel/operation/moveoperation.js';
 import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
 
-const getIteratorCount = coreTestUtils.getIteratorCount;
-
 describe( 'Selection', () => {
 	let attrFooBar;
 
@@ -44,12 +41,14 @@ describe( 'Selection', () => {
 		liveRange.detach();
 	} );
 
-	it( 'should not have any range, anchor or focus position when just created', () => {
+	it( 'should not have any range, attributes, anchor or focus position when just created', () => {
 		let ranges = selection.getRanges();
 
 		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.size ).to.equal( 0 );
 	} );
 
 	it( 'should be collapsed if it has no ranges or all ranges are collapsed', () => {
@@ -411,103 +410,4 @@ describe( 'Selection', () => {
 			} );
 		} );
 	} );
-
-	// Testing integration with attributes list.
-	// Tests copied from AttributeList tests.
-	// Some cases were omitted.
-
-	describe( 'setAttr', () => {
-		it( 'should insert an attribute', () => {
-			selection.setAttr( attrFooBar );
-
-			expect( getIteratorCount( selection.getAttrs() ) ).to.equal( 1 );
-			expect( selection.getAttr( attrFooBar.key ) ).to.equal( attrFooBar.value );
-		} );
-	} );
-
-	describe( 'setAttrsTo', () => {
-		it( 'should remove all attributes and set passed ones', () => {
-			selection.setAttr( attrFooBar );
-
-			let attrs = [ new Attribute( 'abc', true ), new Attribute( 'xyz', false ) ];
-
-			selection.setAttrsTo( attrs );
-
-			expect( getIteratorCount( selection.getAttrs() ) ).to.equal( 2 );
-			expect( selection.getAttr( 'foo' ) ).to.be.null;
-			expect( selection.getAttr( 'abc' ) ).to.be.true;
-			expect( selection.getAttr( 'xyz' ) ).to.be.false;
-		} );
-	} );
-
-	describe( 'getAttr', () => {
-		beforeEach( () => {
-			selection.setAttr( attrFooBar );
-		} );
-
-		it( 'should return attribute value if key of previously set attribute has been passed', () => {
-			expect( selection.getAttr( 'foo' ) ).to.equal( attrFooBar.value );
-		} );
-
-		it( 'should return null if attribute with given key has not been found', () => {
-			expect( selection.getAttr( 'bar' ) ).to.be.null;
-		} );
-	} );
-
-	describe( 'removeAttr', () => {
-		it( 'should remove an attribute', () => {
-			let attrA = new Attribute( 'a', 'A' );
-			let attrB = new Attribute( 'b', 'B' );
-			let attrC = new Attribute( 'c', 'C' );
-
-			selection.setAttr( attrA );
-			selection.setAttr( attrB );
-			selection.setAttr( attrC );
-
-			selection.removeAttr( attrB.key );
-
-			expect( getIteratorCount( selection.getAttrs() ) ).to.equal( 2 );
-			expect( selection.getAttr( attrA.key ) ).to.equal( attrA.value );
-			expect( selection.getAttr( attrC.key ) ).to.equal( attrC.value );
-			expect( selection.getAttr( attrB.key ) ).to.be.null;
-		} );
-	} );
-
-	describe( 'hasAttr', () => {
-		it( 'should check attribute by key', () => {
-			selection.setAttr( attrFooBar );
-			expect( selection.hasAttr( 'foo' ) ).to.be.true;
-		} );
-
-		it( 'should return false if attribute was not found by key', () => {
-			expect( selection.hasAttr( 'bar' ) ).to.be.false;
-		} );
-
-		it( 'should check attribute by object', () => {
-			selection.setAttr( attrFooBar );
-			expect( selection.hasAttr( attrFooBar ) ).to.be.true;
-		} );
-
-		it( 'should return false if attribute was not found by object', () => {
-			expect( selection.hasAttr( attrFooBar ) ).to.be.false;
-		} );
-	} );
-
-	describe( 'getAttrs', () => {
-		it( 'should return all set attributes', () => {
-			let attrA = new Attribute( 'a', 'A' );
-			let attrB = new Attribute( 'b', 'B' );
-			let attrC = new Attribute( 'c', 'C' );
-
-			selection.setAttrsTo( [
-				attrA,
-				attrB,
-				attrC
-			] );
-
-			selection.removeAttr( attrB.key );
-
-			expect( [ attrA, attrC ] ).to.deep.equal( Array.from( selection.getAttrs() ) );
-		} );
-	} );
 } );

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

@@ -9,6 +9,7 @@
 
 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', () => {
@@ -17,8 +18,8 @@ describe( 'Text', () => {
 			let text = new Text( 'bar', attrs );
 
 			expect( text ).to.have.property( 'text' ).that.equals( 'bar' );
-			expect( text ).to.have.property( 'attrs' ).that.is.an( 'array' );
-			expect( text.attrs ).to.be.deep.equals( attrs );
+			expect( text ).to.have.property( 'attrs' ).that.is.instanceof( AttributeList );
+			expect( Array.from( text.attrs ) ).to.deep.equal( attrs );
 		} );
 	} );
 } );