Browse Source

Changed AttributeList API.

Szymon Cofalik 10 năm trước cách đây
mục cha
commit
747aa1fcbb
22 tập tin đã thay đổi với 345 bổ sung562 xóa
  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 );
 		} );
 	} );
 } );