Sfoglia il codice sorgente

Merge pull request #521 from ckeditor/t/441

Changes in model test utilities.
Szymon Cofalik 9 anni fa
parent
commit
f4230ee698

+ 9 - 80
packages/ckeditor5-engine/src/model/liveselection.js

@@ -44,14 +44,6 @@ export default class LiveSelection extends Selection {
 		 * @member {engine.model.Document} engine.model.Selection#_document
 		 */
 		this._document = document;
-
-		/**
-		 * List of attributes set on current selection.
-		 *
-		 * @protected
-		 * @member {Map} engine.model.LiveSelection#_attrs
-		 */
-		this._attrs = new Map();
 	}
 
 	/**
@@ -128,94 +120,37 @@ export default class LiveSelection extends Selection {
 	}
 
 	/**
-	 * Removes all attributes from the selection.
-	 *
-	 * @fires engine.model.LiveSelection#change:attribute
+	 * @inheritDoc
 	 */
 	clearAttributes() {
-		this._attrs.clear();
 		this._setStoredAttributesTo( new Map() );
-
-		this.fire( 'change:attribute' );
-	}
-
-	/**
-	 * Gets an attribute value for given key or `undefined` if that attribute is not set on the selection.
-	 *
-	 * @param {String} key Key of attribute to look for.
-	 * @returns {*} Attribute value or `undefined`.
-	 */
-	getAttribute( key ) {
-		return this._attrs.get( key );
-	}
-
-	/**
-	 * Returns iterator that iterates over this selection attributes.
-	 *
-	 * @returns {Iterable.<*>}
-	 */
-	getAttributes() {
-		return this._attrs[ Symbol.iterator ]();
+		super.clearAttributes();
 	}
 
 	/**
-	 * Checks if the selection has an attribute for given key.
-	 *
-	 * @param {String} key Key of attribute to check.
-	 * @returns {Boolean} `true` if attribute with given key is set on selection, `false` otherwise.
-	 */
-	hasAttribute( key ) {
-		return this._attrs.has( key );
-	}
-
-	/**
-	 * Removes an attribute with given key from the selection.
-	 *
-	 * @fires engine.model.LiveSelection#change:attribute
-	 * @param {String} key Key of attribute to remove.
+	 * @inheritDoc
 	 */
 	removeAttribute( key ) {
-		this._attrs.delete( key );
 		this._removeStoredAttribute( key );
-
-		this.fire( 'change:attribute' );
+		super.removeAttribute( key );
 	}
 
 	/**
-	 * Sets attribute on the selection. If attribute with the same key already is set, it overwrites its values.
-	 *
-	 * @fires engine.model.LiveSelection#change:attribute
-	 * @param {String} key Key of attribute to set.
-	 * @param {*} value Attribute value.
+	 * @inheritDoc
 	 */
 	setAttribute( key, value ) {
-		this._attrs.set( key, value );
 		this._storeAttribute( key, value );
-
-		this.fire( 'change:attribute' );
+		super.setAttribute( key, value );
 	}
 
 	/**
-	 * Removes all attributes from the selection and sets given attributes.
-	 *
-	 * @fires engine.model.LiveSelection#change:attribute
-	 * @param {Iterable|Object} attrs Iterable object containing attributes to be set.
+	 * @inheritDoc
 	 */
 	setAttributesTo( attrs ) {
-		this._attrs = toMap( attrs );
-		this._setStoredAttributesTo( this._attrs );
-
-		this.fire( 'change:attribute' );
+		this._setStoredAttributesTo( toMap( attrs ) );
+		super.setAttributesTo( attrs );
 	}
 
-	/**
-	 * Creates and returns an instance of {@link engine.model.LiveSelection} that is a clone of given selection,
-	 * meaning that it has same ranges and same direction as it.
-	 *
-	 * @params {engine.model.Selection} otherSelection Selection to be cloned.
-	 * @returns {engine.model.LiveSelection} `LiveSelection` instance that is a clone of given selection.
-	 */
-
 	/**
 	 * @inheritDoc
 	 */
@@ -431,9 +366,3 @@ export default class LiveSelection extends Selection {
 		return storePrefix + key;
 	}
 }
-
-/**
- * Fired whenever selection attributes are changed.
- *
- * @event engine.model.LiveSelection#change:attribute
- */

+ 92 - 0
packages/ckeditor5-engine/src/model/selection.js

@@ -10,6 +10,7 @@ import Range from './range.js';
 import EmitterMixin from '../../utils/emittermixin.js';
 import CKEditorError from '../../utils/ckeditorerror.js';
 import mix from '../../utils/mix.js';
+import toMap from '../../utils/tomap.js';
 
 /**
  * `Selection` is a group of {@link engine.model.Range ranges} which has a direction specified by
@@ -37,6 +38,91 @@ export default class Selection {
 		 * @member {Array.<engine.model.Range>} engine.model.Selection#_ranges
 		 */
 		this._ranges = [];
+
+		/**
+		 * List of attributes set on current selection.
+		 *
+		 * @protected
+		 * @member {Map} engine.model.LiveSelection#_attrs
+		 */
+		this._attrs = new Map();
+	}
+
+	/**
+	 * Gets an attribute value for given key or `undefined` if that attribute is not set on the selection.
+	 *
+	 * @param {String} key Key of attribute to look for.
+	 * @returns {*} Attribute value or `undefined`.
+	 */
+	getAttribute( key ) {
+		return this._attrs.get( key );
+	}
+
+	/**
+	 * Returns iterator that iterates over this selection attributes.
+	 *
+	 * @returns {Iterable.<*>}
+	 */
+	getAttributes() {
+		return this._attrs[ Symbol.iterator ]();
+	}
+
+	/**
+	 * Checks if the selection has an attribute for given key.
+	 *
+	 * @param {String} key Key of attribute to check.
+	 * @returns {Boolean} `true` if attribute with given key is set on selection, `false` otherwise.
+	 */
+	hasAttribute( key ) {
+		return this._attrs.has( key );
+	}
+
+	/**
+	 * Removes all attributes from the selection.
+	 *
+	 * @fires engine.model.Selection#change:attribute
+	 */
+	clearAttributes() {
+		this._attrs.clear();
+
+		this.fire( 'change:attribute' );
+	}
+
+	/**
+	 * Removes an attribute with given key from the selection.
+	 *
+	 * @fires engine.model.Selection#change:attribute
+	 * @param {String} key Key of attribute to remove.
+	 */
+	removeAttribute( key ) {
+		this._attrs.delete( key );
+
+		this.fire( 'change:attribute' );
+	}
+
+	/**
+	 * Sets attribute on the selection. If attribute with the same key already is set, it overwrites its values.
+	 *
+	 * @fires engine.model.Selection#change:attribute
+	 * @param {String} key Key of attribute to set.
+	 * @param {*} value Attribute value.
+	 */
+	setAttribute( key, value ) {
+		this._attrs.set( key, value );
+
+		this.fire( 'change:attribute' );
+	}
+
+	/**
+	 * Removes all attributes from the selection and sets given attributes.
+	 *
+	 * @fires engine.model.Selection#change:attribute
+	 * @param {Iterable|Object} attrs Iterable object containing attributes to be set.
+	 */
+	setAttributesTo( attrs ) {
+		this._attrs = toMap( attrs );
+
+		this.fire( 'change:attribute' );
 	}
 
 	/**
@@ -337,3 +423,9 @@ mix( Selection, EmitterMixin );
  *
  * @event engine.model.Selection#change:range
  */
+
+/**
+ * Fired whenever selection attributes are changed.
+ *
+ * @event engine.model.Selection#change:attribute
+ */

+ 22 - 4
packages/ckeditor5-engine/tests/_utils-tests/model.js

@@ -177,6 +177,10 @@ describe( 'model test utils', () => {
 				);
 			} );
 
+			it( 'writes only requested element', () => {
+				expect( stringify( elA ) ).to.equal( '<a></a>' );
+			} );
+
 			it( 'writes selection collapsed in an element', () => {
 				selection.collapse( root );
 
@@ -308,15 +312,15 @@ describe( 'model test utils', () => {
 		test( 'sets elements attributes', {
 			data: '<a foo=1 bar=true car="x y"><b x="y"></b></a>',
 			output: '<a bar=true car="x y" foo=1><b x="y"></b></a>',
-			check( root ) {
-				expect( root.getChild( 0 ).getAttribute( 'car' ) ).to.equal( 'x y' );
+			check( a ) {
+				expect( a.getAttribute( 'car' ) ).to.equal( 'x y' );
 			}
 		} );
 
 		test( 'sets complex attributes', {
 			data: '<a foo={"a":1,"b":"c"}></a>',
-			check( root ) {
-				expect( root.getChild( 0 ).getAttribute( 'foo' ) ).to.have.property( 'a', 1 );
+			check( a ) {
+				expect( a.getAttribute( 'foo' ) ).to.have.property( 'a', 1 );
 			}
 		} );
 
@@ -329,6 +333,20 @@ describe( 'model test utils', () => {
 			}
 		} );
 
+		test( 'returns single parsed element', {
+			data: '<paragraph></paragraph>',
+			check( p ) {
+				expect( p instanceof Element ).to.be.true;
+			}
+		} );
+
+		test( 'returns DocumentFragment for multiple parsed elements', {
+			data: '<paragraph></paragraph><paragraph></paragraph>',
+			check( fragment ) {
+				expect( fragment instanceof DocumentFragment ).to.be.true;
+			}
+		} );
+
 		it( 'throws when unexpected closing tag', () => {
 			expect( () => {
 				parse( '<a><b></a></b>' );

+ 34 - 23
packages/ckeditor5-engine/tests/_utils/model.js

@@ -76,34 +76,39 @@ setData._parse = parse;
  * @returns {String} HTML-like string representing the model.
  */
 export function stringify( node, selectionOrPositionOrRange = null ) {
-	let selection;
-	let document;
-
-	if ( node instanceof RootElement ) {
-		document = node.document;
-	} else if ( node instanceof Element || node instanceof Text ) {
-		// If root is Element or Text - wrap it with DocumentFragment.
-		node = new DocumentFragment( node );
-	}
+	let selection, range;
 
-	document = document || new Document();
+	if ( node instanceof RootElement || node instanceof DocumentFragment ) {
+		range = Range.createFromElement( node );
+	} else {
+		// Node is detached - create new document fragment.
+		if ( !node.parent ) {
+			const fragment = new DocumentFragment( node );
+			range = Range.createFromElement( fragment );
+		} else {
+			range = new Range(
+				Position.createBefore( node ),
+				Position.createAfter( node )
+			);
+		}
+	}
 
 	const walker = new TreeWalker( {
-		boundaries: Range.createFromElement( node )
+		boundaries: range
 	} );
 
 	if ( selectionOrPositionOrRange instanceof Selection ) {
 		selection = selectionOrPositionOrRange;
 	} else if ( selectionOrPositionOrRange instanceof Range ) {
-		selection = document.selection;
+		selection = new Selection();
 		selection.addRange( selectionOrPositionOrRange );
 	} else if ( selectionOrPositionOrRange instanceof Position ) {
-		selection = document.selection;
+		selection = new Selection();
 		selection.addRange( new Range( selectionOrPositionOrRange, selectionOrPositionOrRange ) );
 	}
 
 	let ret = '';
-	let lastPosition = Position.createFromParentAndOffset( node, 0 );
+	let lastPosition = Position.createFromPosition( range.start );
 	const withSelection = !!selection;
 
 	for ( let value of walker ) {
@@ -136,17 +141,18 @@ export function stringify( node, selectionOrPositionOrRange = null ) {
  * and `selection` when selection ranges were included in data to parse.
  */
 export function parse( data, options = {} ) {
-	let document, root;
+	let root, selection;
 	let withSelection = false;
 	const rootName = options.rootName || 'main';
 
 	if ( options.document ) {
-		document = options.document;
+		const document = options.document;
 		root = document.getRoot( rootName );
 		root.removeChildren( 0, root.getChildCount() );
+		selection = document.selection;
 	} else {
-		document = new Document();
-		root = document.createRoot( '$root', rootName );
+		root = new DocumentFragment();
+		selection = new Selection();
 	}
 
 	const path = [];
@@ -189,8 +195,8 @@ export function parse( data, options = {} ) {
 
 		collapsedSelection( token ) {
 			withSelection = true;
-			document.selection.collapse( root, 'END' );
-			document.selection.setAttributesTo( token.attributes );
+			selection.collapse( root, 'END' );
+			selection.setAttributesTo( token.attributes );
 		},
 
 		selectionStart( token ) {
@@ -206,14 +212,14 @@ export function parse( data, options = {} ) {
 			withSelection = true;
 			selectionEnd = Position.createFromParentAndOffset( root, root.getChildCount() );
 
-			document.selection.setRanges(
+			selection.setRanges(
 				[ new Range( selectionStart, selectionEnd ) ],
 				selectionAttributes.backward
 			);
 
 			delete selectionAttributes.backward;
 
-			document.selection.setAttributesTo( selectionAttributes );
+			selection.setAttributesTo( selectionAttributes );
 		}
 	};
 
@@ -229,10 +235,15 @@ export function parse( data, options = {} ) {
 		throw new Error( 'Parse error - missing selection end.' );
 	}
 
+	// If root DocumentFragment contains only one element - return that element.
+	if ( root instanceof DocumentFragment && root.getChildCount() == 1 ) {
+		root = root.getChild( 0 );
+	}
+
 	if ( withSelection ) {
 		return {
 			model: root,
-			selection: document.selection
+			selection: selection
 		};
 	}
 

+ 0 - 101
packages/ckeditor5-engine/tests/model/liveselection.js

@@ -445,14 +445,6 @@ describe( 'LiveSelection', () => {
 		} );
 
 		describe( 'setAttribute', () => {
-			it( 'should set given attribute on the selection', () => {
-				selection.setRanges( [ rangeInFullP ] );
-				selection.setAttribute( 'foo', 'bar' );
-
-				expect( selection.getAttribute( 'foo' ) ).to.equal( 'bar' );
-				expect( fullP.hasAttribute( LiveSelection._getStoreAttributeKey( 'foo' ) ) ).to.be.false;
-			} );
-
 			it( 'should store attribute if the selection is in empty node', () => {
 				selection.setRanges( [ rangeInEmptyP ] );
 				selection.setAttribute( 'foo', 'bar' );
@@ -461,61 +453,9 @@ describe( 'LiveSelection', () => {
 
 				expect( emptyP.getAttribute( LiveSelection._getStoreAttributeKey( 'foo' ) ) ).to.equal( 'bar' );
 			} );
-
-			it( 'should fire change:attribute event', () => {
-				let spy = sinon.spy();
-				selection.on( 'change:attribute', spy );
-
-				selection.setAttribute( 'foo', 'bar' );
-
-				expect( spy.called ).to.be.true;
-			} );
-		} );
-
-		describe( 'hasAttribute', () => {
-			it( 'should return true if element contains attribute with given key', () => {
-				selection.setRanges( [ rangeInFullP ] );
-				selection.setAttribute( 'foo', 'bar' );
-
-				expect( selection.hasAttribute( 'foo' ) ).to.be.true;
-			} );
-
-			it( 'should return false if element does not contain attribute with given key', () => {
-				expect( selection.hasAttribute( 'abc' ) ).to.be.false;
-			} );
-		} );
-
-		describe( 'getAttribute', () => {
-			it( 'should return undefined if element does not contain given attribute', () => {
-				expect( selection.getAttribute( 'abc' ) ).to.be.undefined;
-			} );
-		} );
-
-		describe( 'getAttributes', () => {
-			it( 'should return an iterator that iterates over all attributes set on the text fragment', () => {
-				selection.setRanges( [ rangeInFullP ] );
-				selection.setAttribute( 'foo', 'bar' );
-				selection.setAttribute( 'abc', 'xyz' );
-
-				let attrs = Array.from( selection.getAttributes() );
-
-				expect( attrs ).to.deep.equal( [ [ 'foo', 'bar' ], [ 'abc', 'xyz' ] ] );
-			} );
 		} );
 
 		describe( 'setAttributesTo', () => {
-			it( 'should remove all attributes set on element and set the given ones', () => {
-				selection.setRanges( [ rangeInFullP ] );
-				selection.setAttribute( 'abc', 'xyz' );
-				selection.setAttributesTo( { foo: 'bar' } );
-
-				expect( selection.getAttribute( 'foo' ) ).to.equal( 'bar' );
-				expect( selection.getAttribute( 'abc' ) ).to.be.undefined;
-
-				expect( fullP.hasAttribute( LiveSelection._getStoreAttributeKey( 'foo' ) ) ).to.be.false;
-				expect( fullP.hasAttribute( LiveSelection._getStoreAttributeKey( 'abc' ) ) ).to.be.false;
-			} );
-
 			it( 'should remove all stored attributes and store the given ones if the selection is in empty node', () => {
 				selection.setRanges( [ rangeInEmptyP ] );
 				selection.setAttribute( 'abc', 'xyz' );
@@ -527,15 +467,6 @@ describe( 'LiveSelection', () => {
 				expect( emptyP.getAttribute( LiveSelection._getStoreAttributeKey( 'foo' ) ) ).to.equal( 'bar' );
 				expect( emptyP.hasAttribute( LiveSelection._getStoreAttributeKey( 'abc' ) ) ).to.be.false;
 			} );
-
-			it( 'should fire change:attribute event', () => {
-				let spy = sinon.spy();
-				selection.on( 'change:attribute', spy );
-
-				selection.setAttributesTo( { foo: 'bar' } );
-
-				expect( spy.called ).to.be.true;
-			} );
 		} );
 
 		describe( 'removeAttribute', () => {
@@ -558,32 +489,9 @@ describe( 'LiveSelection', () => {
 
 				expect( emptyP.hasAttribute( LiveSelection._getStoreAttributeKey( 'foo' ) ) ).to.be.false;
 			} );
-
-			it( 'should fire change:attribute event', () => {
-				let spy = sinon.spy();
-				selection.on( 'change:attribute', spy );
-
-				selection.removeAttribute( 'foo' );
-
-				expect( spy.called ).to.be.true;
-			} );
 		} );
 
 		describe( 'clearAttributes', () => {
-			it( 'should remove all attributes from the element', () => {
-				selection.setRanges( [ rangeInFullP ] );
-				selection.setAttribute( 'foo', 'bar' );
-				selection.setAttribute( 'abc', 'xyz' );
-
-				selection.clearAttributes();
-
-				expect( selection.getAttribute( 'foo' ) ).to.be.undefined;
-				expect( selection.getAttribute( 'abc' ) ).to.be.undefined;
-
-				expect( fullP.hasAttribute( LiveSelection._getStoreAttributeKey( 'foo' ) ) ).to.be.false;
-				expect( fullP.hasAttribute( LiveSelection._getStoreAttributeKey( 'abc' ) ) ).to.be.false;
-			} );
-
 			it( 'should remove all stored attributes if the selection is in empty node', () => {
 				selection.setRanges( [ rangeInEmptyP ] );
 				selection.setAttribute( 'foo', 'bar' );
@@ -597,15 +505,6 @@ describe( 'LiveSelection', () => {
 				expect( emptyP.hasAttribute( LiveSelection._getStoreAttributeKey( 'foo' ) ) ).to.be.false;
 				expect( emptyP.hasAttribute( LiveSelection._getStoreAttributeKey( 'abc' ) ) ).to.be.false;
 			} );
-
-			it( 'should fire change:attribute event', () => {
-				let spy = sinon.spy();
-				selection.on( 'change:attribute', spy );
-
-				selection.clearAttributes();
-
-				expect( spy.called ).to.be.true;
-			} );
 		} );
 	} );
 

+ 142 - 0
packages/ckeditor5-engine/tests/model/selection.js

@@ -248,6 +248,34 @@ describe( 'Selection', () => {
 		} );
 	} );
 
+	describe( 'focus', () => {
+		let r3;
+		beforeEach( () => {
+			const r1 = Range.createFromParentsAndOffsets( root, 2, root, 4 );
+			const r2 = Range.createFromParentsAndOffsets( root, 4, root, 6 );
+			r3 = Range.createFromParentsAndOffsets( root, 1, root, 2 );
+			selection.addRange( r1 );
+			selection.addRange( r2 );
+		} );
+
+		it( 'should return correct focus when last added range is not backward one', () => {
+			selection.addRange( r3 );
+
+			expect( selection.focus.isEqual( r3.end ) ).to.be.true;
+		} );
+
+		it( 'should return correct focus when last added range is backward one', () => {
+			selection.addRange( r3, true );
+
+			expect( selection.focus.isEqual( r3.start ) ).to.be.true;
+		} );
+
+		it( 'should return null if no ranges in selection', () => {
+			selection.removeAllRanges();
+			expect( selection.focus ).to.be.null;
+		} );
+	} );
+
 	describe( 'setFocus', () => {
 		it( 'keeps all existing ranges and fires no change:range when no modifications needed', () => {
 			selection.addRange( range );
@@ -553,4 +581,118 @@ describe( 'Selection', () => {
 			}
 		} );
 	} );
+
+	describe( 'attributes interface', () => {
+		let rangeInFullP;
+
+		beforeEach( () => {
+			root.insertChildren( 0, [
+				new Element( 'p', [], 'foobar' ),
+				new Element( 'p', [], [] )
+			] );
+
+			rangeInFullP = new Range( new Position( root, [ 0, 4 ] ), new Position( root, [ 0, 4 ] ) );
+		} );
+
+		describe( 'setAttribute', () => {
+			it( 'should set given attribute on the selection', () => {
+				selection.setRanges( [ rangeInFullP ] );
+				selection.setAttribute( 'foo', 'bar' );
+
+				expect( selection.getAttribute( 'foo' ) ).to.equal( 'bar' );
+			} );
+
+			it( 'should fire change:attribute event', () => {
+				let spy = sinon.spy();
+				selection.on( 'change:attribute', spy );
+
+				selection.setAttribute( 'foo', 'bar' );
+
+				expect( spy.called ).to.be.true;
+			} );
+		} );
+
+		describe( 'getAttribute', () => {
+			it( 'should return undefined if element does not contain given attribute', () => {
+				expect( selection.getAttribute( 'abc' ) ).to.be.undefined;
+			} );
+		} );
+
+		describe( 'getAttributes', () => {
+			it( 'should return an iterator that iterates over all attributes set on the text fragment', () => {
+				selection.setRanges( [ rangeInFullP ] );
+				selection.setAttribute( 'foo', 'bar' );
+				selection.setAttribute( 'abc', 'xyz' );
+
+				let attrs = Array.from( selection.getAttributes() );
+
+				expect( attrs ).to.deep.equal( [ [ 'foo', 'bar' ], [ 'abc', 'xyz' ] ] );
+			} );
+		} );
+
+		describe( 'hasAttribute', () => {
+			it( 'should return true if element contains attribute with given key', () => {
+				selection.setRanges( [ rangeInFullP ] );
+				selection.setAttribute( 'foo', 'bar' );
+
+				expect( selection.hasAttribute( 'foo' ) ).to.be.true;
+			} );
+
+			it( 'should return false if element does not contain attribute with given key', () => {
+				expect( selection.hasAttribute( 'abc' ) ).to.be.false;
+			} );
+		} );
+
+		describe( 'clearAttributes', () => {
+			it( 'should remove all attributes from the element', () => {
+				selection.setRanges( [ rangeInFullP ] );
+				selection.setAttribute( 'foo', 'bar' );
+				selection.setAttribute( 'abc', 'xyz' );
+
+				selection.clearAttributes();
+
+				expect( selection.getAttribute( 'foo' ) ).to.be.undefined;
+				expect( selection.getAttribute( 'abc' ) ).to.be.undefined;
+			} );
+
+			it( 'should fire change:attribute event', () => {
+				let spy = sinon.spy();
+				selection.on( 'change:attribute', spy );
+
+				selection.clearAttributes();
+
+				expect( spy.called ).to.be.true;
+			} );
+		} );
+
+		describe( 'removeAttribute', () => {
+			it( 'should remove attribute', () => {
+				selection.setRanges( [ rangeInFullP ] );
+				selection.setAttribute( 'foo', 'bar' );
+				selection.removeAttribute( 'foo' );
+
+				expect( selection.getAttribute( 'foo' ) ).to.be.undefined;
+			} );
+
+			it( 'should fire change:attribute event', () => {
+				let spy = sinon.spy();
+				selection.on( 'change:attribute', spy );
+
+				selection.removeAttribute( 'foo' );
+
+				expect( spy.called ).to.be.true;
+			} );
+		} );
+
+		describe( 'setAttributesTo', () => {
+			it( 'should remove all attributes set on element and set the given ones', () => {
+				selection.setRanges( [ rangeInFullP ] );
+				selection.setAttribute( 'abc', 'xyz' );
+				selection.setAttributesTo( { foo: 'bar' } );
+
+				expect( selection.getAttribute( 'foo' ) ).to.equal( 'bar' );
+				expect( selection.getAttribute( 'abc' ) ).to.be.undefined;
+			} );
+		} );
+	} );
 } );