Socket
Socket
Sign inDemoInstall

@ckeditor/ckeditor5-utils

Package Overview
Dependencies
0
Maintainers
1
Versions
518
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.8.0 to 0.9.0

src/first.js

13

CHANGELOG.md
Changelog
=========
## [0.9.0](https://github.com/ckeditor/ckeditor5-utils/compare/v0.8.0...v0.9.0) (2017-04-05)
### Bug fixes
* The `getOptimalPosition()` utility should work fine when the parent element has a scroll. Closes [#139](https://github.com/ckeditor/ckeditor5-utils/issues/139). ([b878949](https://github.com/ckeditor/ckeditor5-utils/commit/b878949))
### Features
* `Collection.bindTo()` method now is not only available in the `ViewCollection` but in all `Collection`s. Closes [#125](https://github.com/ckeditor/ckeditor5-utils/issues/125). ([4e299be](https://github.com/ckeditor/ckeditor5-utils/commit/4e299be))
* Added the `first()` function. Closes [#130](https://github.com/ckeditor/ckeditor5-utils/issues/130). ([8ab07d2](https://github.com/ckeditor/ckeditor5-utils/commit/8ab07d2))
* Two–way data binding between `Collection` instances. Closes [#132](https://github.com/ckeditor/ckeditor5-utils/issues/132). ([6b79624](https://github.com/ckeditor/ckeditor5-utils/commit/6b79624))
## [0.8.0](https://github.com/ckeditor/ckeditor5-utils/compare/v0.7.0...v0.8.0) (2017-03-06)

@@ -5,0 +18,0 @@

6

package.json
{
"name": "@ckeditor/ckeditor5-utils",
"version": "0.8.0",
"version": "0.9.0",
"description": "CKEditor 5 Utils",

@@ -10,4 +10,4 @@ "keywords": [

"@ckeditor/ckeditor5-dev-lint": "^2.0.2",
"@ckeditor/ckeditor5-core": "^0.7.0",
"@ckeditor/ckeditor5-engine": "^0.8.0",
"@ckeditor/ckeditor5-core": "^0.8.0",
"@ckeditor/ckeditor5-engine": "^0.9.0",
"del": "^2.2.0",

@@ -14,0 +14,0 @@ "gulp": "^3.9.0",

@@ -31,3 +31,2 @@ /**

*
* @param {Iterable} [items] Items to be added to the collection.
* @param {Object} options The options object.

@@ -60,2 +59,34 @@ * @param {String} [options.idProperty='id'] The name of the property which is considered to identify an item.

this._idProperty = options && options.idProperty || 'id';
/**
* A helper mapping external items of a bound collection ({@link #bindTo})
* and actual items of this collection. It provides information
* necessary to properly remove items bound to another collection.
*
* See {@link #_bindToInternalToExternalMap}.
*
* @protected
* @member {WeakMap}
*/
this._bindToExternalToInternalMap = new WeakMap();
/**
* A helper mapping items of this collection to external items of a bound collection
* ({@link #bindTo}). It provides information necessary to manage the bindings, e.g.
* to avoid loops in two–way bindings.
*
* See {@link #_bindToExternalToInternalMap}.
*
* @protected
* @member {WeakMap}
*/
this._bindToInternalToExternalMap = new WeakMap();
/**
* A collection instance this collection is bound to as a result
* of calling {@link #bindTo} method.
*
* @protected
* @member {module:utils/collection~Collection} #_bindToCollection
*/
}

@@ -223,2 +254,6 @@

const externalItem = this._bindToInternalToExternalMap.get( item );
this._bindToInternalToExternalMap.delete( item );
this._bindToExternalToInternalMap.delete( externalItem );
this.fire( 'remove', item );

@@ -269,5 +304,11 @@

/**
* Removes all items from the collection.
* Removes all items from the collection and destroys the binding created using
* {@link #bindTo}.
*/
clear() {
if ( this._bindToCollection ) {
this.stopListening( this._bindToCollection );
this._bindToCollection = null;
}
while ( this.length ) {

@@ -279,2 +320,173 @@ this.remove( 0 );

/**
* Binds and synchronizes the collection with another one.
*
* The binding can be a simple factory:
*
* class FactoryClass {
* constructor( data ) {
* this.label = data.label;
* }
* }
*
* const source = new Collection( { idProperty: 'label' } );
* const target = new Collection();
*
* target.bindTo( source ).as( FactoryClass );
*
* source.add( { label: 'foo' } );
* source.add( { label: 'bar' } );
*
* console.log( target.length ); // 2
* console.log( target.get( 1 ).label ); // 'bar'
*
* source.remove( 0 );
* console.log( target.length ); // 1
* console.log( target.get( 0 ).label ); // 'bar'
*
* or the factory driven by a custom callback:
*
* class FooClass {
* constructor( data ) {
* this.label = data.label;
* }
* }
*
* class BarClass {
* constructor( data ) {
* this.label = data.label;
* }
* }
*
* const source = new Collection( { idProperty: 'label' } );
* const target = new Collection();
*
* target.bindTo( source ).using( ( item ) => {
* if ( item.label == 'foo' ) {
* return new FooClass( item );
* } else {
* return new BarClass( item );
* }
* } );
*
* source.add( { label: 'foo' } );
* source.add( { label: 'bar' } );
*
* console.log( target.length ); // 2
* console.log( target.get( 0 ) instanceof FooClass ); // true
* console.log( target.get( 1 ) instanceof BarClass ); // true
*
* or the factory out of property name:
*
* const source = new Collection( { idProperty: 'label' } );
* const target = new Collection();
*
* target.bindTo( source ).using( 'label' );
*
* source.add( { label: { value: 'foo' } } );
* source.add( { label: { value: 'bar' } } );
*
* console.log( target.length ); // 2
* console.log( target.get( 0 ).value ); // 'foo'
* console.log( target.get( 1 ).value ); // 'bar'
*
* **Note**: {@link #clear} can be used to break the binding.
*
* @param {module:utils/collection~Collection} collection A collection to be bound.
* @returns {Object}
* @returns {module:utils/collection~Collection#bindTo#as} return.as
* @returns {module:utils/collection~Collection#bindTo#using} return.using
*/
bindTo( externalCollection ) {
if ( this._bindToCollection ) {
/**
* The collection cannot be bound more than once.
*
* @error collection-bind-to-rebind
*/
throw new CKEditorError( 'collection-bind-to-rebind: The collection cannot be bound more than once.' );
}
this._bindToCollection = externalCollection;
return {
/**
* Creates the class factory binding.
*
* @static
* @param {Function} Class Specifies which class factory is to be initialized.
*/
as: ( Class ) => {
this._setUpBindToBinding( item => new Class( item ) );
},
/**
* Creates callback or property binding.
*
* @static
* @param {Function|String} callbackOrProperty When the function is passed, it is used to
* produce the items. When the string is provided, the property value is used to create
* the bound collection items.
*/
using: ( callbackOrProperty ) => {
if ( typeof callbackOrProperty == 'function' ) {
this._setUpBindToBinding( item => callbackOrProperty( item ) );
} else {
this._setUpBindToBinding( item => item[ callbackOrProperty ] );
}
}
};
}
/**
* Finalizes and activates a binding initiated by {#bindTo}.
*
* @protected
* @param {Function} factory A function which produces collection items.
*/
_setUpBindToBinding( factory ) {
const externalCollection = this._bindToCollection;
// Adds the item to the collection once a change has been done to the external collection.
//
// @private
const addItem = ( evt, externalItem, index ) => {
const isExternalBoundToThis = externalCollection._bindToCollection == this;
const externalItemBound = externalCollection._bindToInternalToExternalMap.get( externalItem );
// If an external collection is bound to this collection, which makes it a 2–way binding,
// and the particular external collection item is already bound, don't add it here.
// The external item has been created **out of this collection's item** and (re)adding it will
// cause a loop.
if ( isExternalBoundToThis && externalItemBound ) {
this._bindToExternalToInternalMap.set( externalItem, externalItemBound );
this._bindToInternalToExternalMap.set( externalItemBound, externalItem );
} else {
const item = factory( externalItem );
this._bindToExternalToInternalMap.set( externalItem, item );
this._bindToInternalToExternalMap.set( item, externalItem );
this.add( item, index );
}
};
// Load the initial content of the collection.
for ( let externalItem of externalCollection ) {
addItem( null, externalItem );
}
// Synchronize the with collection as new items are added.
this.listenTo( externalCollection, 'add', addItem );
// Synchronize the with collection as new items are removed.
this.listenTo( externalCollection, 'remove', ( evt, externalItem ) => {
const item = this._bindToExternalToInternalMap.get( externalItem );
if ( item ) {
this.remove( item );
}
} );
}
/**
* Collection iterator.

@@ -281,0 +493,0 @@ */

@@ -102,10 +102,28 @@ /**

// (#126) If there's some positioned ancestor of the panel, then its rect must be taken into
// consideration. `Rect` is always relative to the viewport while `position: absolute` works
// with respect to that positioned ancestor.
if ( positionedElementAncestor ) {
const ancestorPosition = getAbsoluteRectCoordinates( new Rect( positionedElementAncestor ) );
const ancestorComputedStyles = global.window.getComputedStyle( positionedElementAncestor );
// (https://github.com/ckeditor/ckeditor5-ui-default/issues/126)
// If there's some positioned ancestor of the panel, then its `Rect` must be taken into
// consideration. `Rect` is always relative to the viewport while `position: absolute` works
// with respect to that positioned ancestor.
left -= ancestorPosition.left;
top -= ancestorPosition.top;
// (https://github.com/ckeditor/ckeditor5-utils/issues/139)
// If there's some positioned ancestor of the panel, not only its position must be taken into
// consideration (see above) but also its internal scrolls. Scroll have an impact here because `Rect`
// is relative to the viewport (it doesn't care about scrolling), while `position: absolute`
// must compensate that scrolling.
left += positionedElementAncestor.scrollLeft;
top += positionedElementAncestor.scrollTop;
// (https://github.com/ckeditor/ckeditor5-utils/issues/139)
// If there's some positioned ancestor of the panel, then its `Rect` includes its CSS `borderWidth`
// while `position: absolute` positioning does not consider it.
// E.g. `{ position: absolute, top: 0, left: 0 }` means upper left corner of the element,
// not upper-left corner of its border.
left -= parseInt( ancestorComputedStyles.borderLeftWidth, 10 );
top -= parseInt( ancestorComputedStyles.borderTopWidth, 10 );
}

@@ -112,0 +130,0 @@

@@ -35,3 +35,3 @@ /**

*
* @private
* @protected
* @member {module:utils/dom/emittermixin~Emitter}

@@ -38,0 +38,0 @@ */

@@ -11,4 +11,9 @@ /**

/**
* Returns `nth` (starts from `0` of course) item of an `iterable`.
* Returns `nth` (starts from `0` of course) item of the given `iterable`.
*
* If the iterable is a generator, then it consumes **all its items**.
* If it's a normal iterator, then it consumes **all items up to the given index**.
* Refer to the [Iterators and Generators](https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Iterators_and_Generators)
* guide to learn differences between these interfaces.
*
* @param {Number} index

@@ -19,3 +24,3 @@ * @param {Iterable.<*>} iterable

export default function nth( index, iterable ) {
for ( let item of iterable ) {
for ( const item of iterable ) {
if ( index === 0 ) {

@@ -22,0 +27,0 @@ return item;

@@ -27,3 +27,3 @@ /**

describe( 'constructor', () => {
describe( 'constructor()', () => {
it( 'allows to change the id property used by the collection', () => {

@@ -44,3 +44,3 @@ let item1 = { id: 'foo', name: 'xx' };

describe( 'add', () => {
describe( 'add()', () => {
it( 'should be chainable', () => {

@@ -211,3 +211,2 @@ expect( collection.add( {} ) ).to.equal( collection );

it( 'should support an optional index argument', () => {
let collection = new Collection();
let item1 = getItem( 'foo' );

@@ -230,3 +229,2 @@ let item2 = getItem( 'bar' );

it( 'should throw when index argument is invalid', () => {
let collection = new Collection();
let item1 = getItem( 'foo' );

@@ -267,3 +265,3 @@ let item2 = getItem( 'bar' );

describe( 'get', () => {
describe( 'get()', () => {
it( 'should return an item', () => {

@@ -289,3 +287,3 @@ let item = getItem( 'foo' );

describe( 'getIndex', () => {
describe( 'getIndex()', () => {
it( 'should return index of given item', () => {

@@ -326,3 +324,3 @@ const item1 = { foo: 'bar' };

describe( 'remove', () => {
describe( 'remove()', () => {
it( 'should remove the model by index', () => {

@@ -454,3 +452,3 @@ collection.add( getItem( 'bom' ) );

describe( 'map', () => {
describe( 'map()', () => {
it( 'uses native map', () => {

@@ -471,3 +469,3 @@ let spy = testUtils.sinon.stub( Array.prototype, 'map', () => {

describe( 'find', () => {
describe( 'find()', () => {
it( 'uses native find', () => {

@@ -490,3 +488,3 @@ let needl = getItem( 'foo' );

describe( 'filter', () => {
describe( 'filter()', () => {
it( 'uses native filter', () => {

@@ -509,3 +507,3 @@ let needl = getItem( 'foo' );

describe( 'clear', () => {
describe( 'clear()', () => {
it( 'removes all items', () => {

@@ -524,4 +522,492 @@ const items = [ {}, {}, {} ];

} );
it( 'breaks the binding', () => {
const external = new Collection();
collection.bindTo( external ).using( i => i );
external.add( { foo: 'bar' } );
expect( collection ).to.have.length( 1 );
collection.clear();
external.add( { foo: 'baz' } );
expect( collection ).to.have.length( 0 );
external.remove( 0 );
expect( collection ).to.have.length( 0 );
expect( collection._bindToCollection ).to.be.null;
} );
} );
describe( 'bindTo()', () => {
class FactoryClass {
constructor( data ) {
this.data = data;
}
}
function assertItems( collection, expectedItems ) {
expect( collection.map( i => i.v ) ).to.deep.equal( expectedItems );
}
it( 'throws when binding more than once', () => {
collection.bindTo( {} );
expect( () => {
collection.bindTo( {} );
} ).to.throw( CKEditorError, /^collection-bind-to-rebind/ );
} );
it( 'provides "using()" and "as()" interfaces', () => {
const returned = collection.bindTo( {} );
expect( returned ).to.have.keys( 'using', 'as' );
expect( returned.using ).to.be.a( 'function' );
expect( returned.as ).to.be.a( 'function' );
} );
it( 'stores reference to bound collection', () => {
const collectionB = new Collection();
expect( collection._bindToCollection ).to.be.undefined;
expect( collectionB._bindToCollection ).to.be.undefined;
collection.bindTo( collectionB ).as( FactoryClass );
expect( collection._bindToCollection ).to.equal( collectionB );
expect( collectionB._bindToCollection ).to.be.undefined;
} );
describe( 'as()', () => {
let items;
beforeEach( () => {
items = new Collection();
} );
it( 'does not chain', () => {
const returned = collection.bindTo( new Collection() ).as( FactoryClass );
expect( returned ).to.be.undefined;
} );
it( 'creates a binding (initial content)', () => {
items.add( { id: '1' } );
items.add( { id: '2' } );
collection.bindTo( items ).as( FactoryClass );
expect( collection ).to.have.length( 2 );
expect( collection.get( 0 ) ).to.be.instanceOf( FactoryClass );
expect( collection.get( 1 ) ).to.be.instanceOf( FactoryClass );
expect( collection.get( 1 ).data ).to.equal( items.get( 1 ) );
} );
it( 'creates a binding (new content)', () => {
collection.bindTo( items ).as( FactoryClass );
expect( collection ).to.have.length( 0 );
items.add( { id: '1' } );
items.add( { id: '2' } );
expect( collection ).to.have.length( 2 );
expect( collection.get( 0 ) ).to.be.instanceOf( FactoryClass );
expect( collection.get( 1 ) ).to.be.instanceOf( FactoryClass );
expect( collection.get( 1 ).data ).to.equal( items.get( 1 ) );
} );
it( 'creates a binding (item removal)', () => {
collection.bindTo( items ).as( FactoryClass );
expect( collection ).to.have.length( 0 );
items.add( { id: '1' } );
items.add( { id: '2' } );
expect( collection ).to.have.length( 2 );
expect( collection.get( 0 ) ).to.be.instanceOf( FactoryClass );
expect( collection.get( 1 ) ).to.be.instanceOf( FactoryClass );
expect( collection.get( 1 ).data ).to.equal( items.get( 1 ) );
items.remove( 1 );
expect( collection.get( 0 ).data ).to.equal( items.get( 0 ) );
items.remove( 0 );
expect( collection ).to.have.length( 0 );
} );
} );
describe( 'using()', () => {
let items;
beforeEach( () => {
items = new Collection();
} );
it( 'does not chain', () => {
const returned = collection.bindTo( new Collection() ).using( () => {} );
expect( returned ).to.be.undefined;
} );
describe( 'callback', () => {
it( 'creates a binding (arrow function)', () => {
collection.bindTo( items ).using( ( item ) => {
return new FactoryClass( item );
} );
expect( collection ).to.have.length( 0 );
items.add( { id: '1' } );
items.add( { id: '2' } );
expect( collection ).to.have.length( 2 );
expect( collection.get( 0 ) ).to.be.instanceOf( FactoryClass );
expect( collection.get( 1 ) ).to.be.instanceOf( FactoryClass );
expect( collection.get( 1 ).data ).to.equal( items.get( 1 ) );
} );
// https://github.com/ckeditor/ckeditor5-ui/issues/113
it( 'creates a binding (normal function)', () => {
collection.bindTo( items ).using( function( item ) {
return new FactoryClass( item );
} );
items.add( { id: '1' } );
expect( collection ).to.have.length( 1 );
const view = collection.get( 0 );
// Wrong args will be passed to the callback if it's treated as the view constructor.
expect( view ).to.be.instanceOf( FactoryClass );
expect( view.data ).to.equal( items.get( 0 ) );
} );
it( 'creates a 1:1 binding', () => {
collection.bindTo( items ).using( item => item );
expect( collection ).to.have.length( 0 );
const item1 = { id: '100' };
const item2 = { id: '200' };
items.add( item1 );
items.add( item2 );
expect( collection ).to.have.length( 2 );
expect( collection.get( 0 ) ).to.equal( item1 );
expect( collection.get( 1 ) ).to.equal( item2 );
} );
it( 'creates a conditional binding', () => {
class CustomClass {
constructor( data ) {
this.data = data;
}
}
collection.bindTo( items ).using( item => {
if ( item.id == 'FactoryClass' ) {
return new FactoryClass( item );
} else {
return new CustomClass( item );
}
} );
expect( collection ).to.have.length( 0 );
const item1 = { id: 'FactoryClass' };
const item2 = { id: 'CustomClass' };
items.add( item1 );
items.add( item2 );
expect( collection ).to.have.length( 2 );
expect( collection.get( 0 ) ).to.be.instanceOf( FactoryClass );
expect( collection.get( 1 ) ).to.be.instanceOf( CustomClass );
} );
it( 'creates a binding to a property name', () => {
collection.bindTo( items ).using( item => item.prop );
expect( collection ).to.have.length( 0 );
items.add( { prop: { value: 'foo' } } );
items.add( { prop: { value: 'bar' } } );
expect( collection ).to.have.length( 2 );
expect( collection.get( 0 ).value ).to.equal( 'foo' );
expect( collection.get( 1 ).value ).to.equal( 'bar' );
} );
} );
describe( 'property name', () => {
it( 'creates a binding', () => {
collection.bindTo( items ).using( 'prop' );
expect( collection ).to.have.length( 0 );
items.add( { prop: { value: 'foo' } } );
items.add( { prop: { value: 'bar' } } );
expect( collection ).to.have.length( 2 );
expect( collection.get( 0 ).value ).to.equal( 'foo' );
expect( collection.get( 1 ).value ).to.equal( 'bar' );
} );
it( 'creates a binding (item removal)', () => {
collection.bindTo( items ).using( 'prop' );
expect( collection ).to.have.length( 0 );
items.add( { prop: { value: 'foo' } } );
items.add( { prop: { value: 'bar' } } );
expect( collection ).to.have.length( 2 );
expect( collection.get( 0 ).value ).to.equal( 'foo' );
expect( collection.get( 1 ).value ).to.equal( 'bar' );
items.remove( 1 );
expect( collection ).to.have.length( 1 );
expect( collection.get( 0 ).value ).to.equal( 'foo' );
items.remove( 0 );
expect( collection ).to.have.length( 0 );
} );
} );
} );
describe( 'two–way data binding', () => {
it( 'works with custom factories (1)', () => {
const collectionA = new Collection();
const collectionB = new Collection();
const spyA = sinon.spy();
const spyB = sinon.spy();
collectionA.on( 'add', spyA );
collectionB.on( 'add', spyB );
// A<--->B
collectionA.bindTo( collectionB ).using( i => ( { v: i.v * 2 } ) );
collectionB.bindTo( collectionA ).using( i => ( { v: i.v / 2 } ) );
assertItems( collectionA, [] );
assertItems( collectionB, [] );
collectionA.add( { v: 4 } );
collectionA.add( { v: 6 } );
assertItems( collectionA, [ 4, 6 ] );
assertItems( collectionB, [ 2, 3 ] );
collectionB.add( { v: 4 } );
assertItems( collectionA, [ 4, 6, 8 ] );
assertItems( collectionB, [ 2, 3, 4 ] );
sinon.assert.callCount( spyA, 3 );
sinon.assert.callCount( spyB, 3 );
} );
it( 'works with custom factories (2)', () => {
const collectionA = new Collection();
const collectionB = new Collection();
const spyA = sinon.spy();
const spyB = sinon.spy();
collectionA.on( 'add', spyA );
collectionB.on( 'add', spyB );
// A<--->B
collectionA.bindTo( collectionB ).using( 'data' );
collectionB.bindTo( collectionA ).using( i => new FactoryClass( i ) );
collectionA.add( { v: 4 } );
collectionA.add( { v: 6 } );
expect( [ ...collectionB ].every( i => i instanceof FactoryClass ) ).to.be.true;
expect( [ ...collectionB ].map( i => i.data ) ).to.deep.equal( [ ...collectionA ] );
expect( collectionB.map( i => i.data.v ) ).to.deep.equal( [ 4, 6 ] );
expect( collectionA.map( i => i.v ) ).to.deep.equal( [ 4, 6 ] );
collectionB.add( new FactoryClass( { v: 8 } ) );
expect( [ ...collectionB ].every( i => i instanceof FactoryClass ) ).to.be.true;
expect( [ ...collectionB ].map( i => i.data ) ).to.deep.equal( [ ...collectionA ] );
expect( collectionB.map( i => i.data.v ) ).to.deep.equal( [ 4, 6, 8 ] );
expect( collectionA.map( i => i.v ) ).to.deep.equal( [ 4, 6, 8 ] );
} );
it( 'works with custom factories (custom index)', () => {
const collectionA = new Collection();
const collectionB = new Collection();
const spyA = sinon.spy();
const spyB = sinon.spy();
collectionA.on( 'add', spyA );
collectionB.on( 'add', spyB );
// A<--->B
collectionA.bindTo( collectionB ).using( i => ( { v: i.v * 2 } ) );
collectionB.bindTo( collectionA ).using( i => ( { v: i.v / 2 } ) );
assertItems( collectionA, [] );
assertItems( collectionB, [] );
collectionA.add( { v: 4 } );
collectionA.add( { v: 6 }, 0 );
assertItems( collectionA, [ 6, 4 ] );
assertItems( collectionB, [ 3, 2 ] );
collectionB.add( { v: 4 }, 1 );
assertItems( collectionA, [ 6, 8, 4 ] );
assertItems( collectionB, [ 3, 4, 2 ] );
sinon.assert.callCount( spyA, 3 );
sinon.assert.callCount( spyB, 3 );
} );
it( 'works with 1:1 binding', () => {
const collectionA = new Collection();
const collectionB = new Collection();
const spyA = sinon.spy();
const spyB = sinon.spy();
collectionA.on( 'add', spyA );
collectionB.on( 'add', spyB );
// A<--->B
collectionA.bindTo( collectionB ).using( i => i );
collectionB.bindTo( collectionA ).using( i => i );
assertItems( collectionA, [], [] );
assertItems( collectionB, [], [] );
collectionA.add( { v: 4 } );
collectionA.add( { v: 6 } );
assertItems( collectionA, [ 4, 6 ] );
assertItems( collectionB, [ 4, 6 ] );
collectionB.add( { v: 8 } );
assertItems( collectionA, [ 4, 6, 8 ] );
assertItems( collectionB, [ 4, 6, 8 ] );
expect( collectionA ).to.deep.equal( collectionB );
sinon.assert.callCount( spyA, 3 );
sinon.assert.callCount( spyB, 3 );
} );
it( 'works with double chaining', () => {
const collectionA = new Collection();
const collectionB = new Collection();
const collectionC = new Collection();
const spyA = sinon.spy();
const spyB = sinon.spy();
const spyC = sinon.spy();
collectionA.on( 'add', spyA );
collectionB.on( 'add', spyB );
collectionC.on( 'add', spyC );
// A<--->B--->C
collectionA.bindTo( collectionB ).using( i => ( { v: i.v * 2 } ) );
collectionB.bindTo( collectionA ).using( i => ( { v: i.v / 2 } ) );
collectionC.bindTo( collectionB ).using( i => ( { v: -i.v } ) );
assertItems( collectionA, [] );
assertItems( collectionB, [] );
assertItems( collectionC, [] );
collectionA.add( { v: 4 } );
collectionA.add( { v: 6 } );
assertItems( collectionA, [ 4, 6 ] );
assertItems( collectionB, [ 2, 3 ] );
assertItems( collectionC, [ -2, -3 ] );
collectionB.add( { v: 4 } );
assertItems( collectionA, [ 4, 6, 8 ] );
assertItems( collectionB, [ 2, 3, 4 ] );
assertItems( collectionC, [ -2, -3, -4 ] );
collectionC.add( { v: -1000 } );
assertItems( collectionA, [ 4, 6, 8 ] );
assertItems( collectionB, [ 2, 3, 4 ] );
assertItems( collectionC, [ -2, -3, -4, -1000 ] );
sinon.assert.callCount( spyA, 3 );
sinon.assert.callCount( spyB, 3 );
sinon.assert.callCount( spyC, 4 );
} );
it( 'removes items correctly', () => {
const collectionA = new Collection();
const collectionB = new Collection();
const spyAddA = sinon.spy();
const spyAddB = sinon.spy();
const spyRemoveA = sinon.spy();
const spyRemoveB = sinon.spy();
collectionA.on( 'add', spyAddA );
collectionB.on( 'add', spyAddB );
collectionA.on( 'remove', spyRemoveA );
collectionB.on( 'remove', spyRemoveB );
// A<--->B
collectionA.bindTo( collectionB ).using( i => ( { v: i.v * 2 } ) );
collectionB.bindTo( collectionA ).using( i => ( { v: i.v / 2 } ) );
assertItems( collectionA, [], [] );
assertItems( collectionB, [], [] );
collectionA.add( { v: 4 } );
collectionA.add( { v: 6 } );
assertItems( collectionA, [ 4, 6 ] );
assertItems( collectionB, [ 2, 3 ] );
collectionB.add( { v: 4 } );
assertItems( collectionA, [ 4, 6, 8 ] );
assertItems( collectionB, [ 2, 3, 4 ] );
collectionB.remove( 0 );
assertItems( collectionA, [ 6, 8 ] );
assertItems( collectionB, [ 3, 4 ] );
sinon.assert.callCount( spyAddA, 3 );
sinon.assert.callCount( spyAddB, 3 );
sinon.assert.callCount( spyRemoveA, 1 );
sinon.assert.callCount( spyRemoveB, 1 );
collectionA.remove( 1 );
assertItems( collectionA, [ 6 ] );
assertItems( collectionB, [ 3 ] );
sinon.assert.callCount( spyAddA, 3 );
sinon.assert.callCount( spyAddB, 3 );
sinon.assert.callCount( spyRemoveA, 2 );
sinon.assert.callCount( spyRemoveB, 2 );
} );
} );
} );
describe( 'iterator', () => {

@@ -528,0 +1014,0 @@ it( 'covers the whole collection', () => {

@@ -6,15 +6,10 @@ /**

/* global document, window */
import global from '../../src/dom/global';
import { getOptimalPosition } from '../../src/dom/position';
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
testUtils.createSinonSandbox();
let element, target, limiter;
let element, target, limiter, windowStub;
describe( 'getOptimalPosition', () => {
describe( 'getOptimalPosition()', () => {
beforeEach( () => {
windowStub = {
stubWindow( {
innerWidth: 10000,

@@ -24,5 +19,3 @@ innerHeight: 10000,

scrollY: 0
};
testUtils.sinon.stub( global, 'window', windowStub );
} );
} );

@@ -42,3 +35,3 @@

it( 'should return coordinates (window scroll)', () => {
Object.assign( windowStub, {
stubWindow( {
innerWidth: 10000,

@@ -57,37 +50,63 @@ innerHeight: 10000,

it( 'should return coordinates (positioned element parent)', () => {
const positionedParent = document.createElement( 'div' );
describe( 'positioned element parent', () => {
let parent;
Object.assign( windowStub, {
innerWidth: 10000,
innerHeight: 10000,
scrollX: 1000,
scrollY: 1000,
getComputedStyle: ( el ) => {
return window.getComputedStyle( el );
}
} );
it( 'should return coordinates', () => {
stubWindow( {
innerWidth: 10000,
innerHeight: 10000,
scrollX: 1000,
scrollY: 1000
} );
Object.assign( positionedParent.style, {
position: 'absolute',
top: '1000px',
left: '1000px'
} );
parent = getElement( {
top: 1000,
right: 1010,
bottom: 1010,
left: 1000,
width: 10,
height: 10
}, {
position: 'absolute'
} );
document.body.appendChild( positionedParent );
positionedParent.appendChild( element );
element.parentElement = parent;
stubElementRect( positionedParent, {
top: 1000,
right: 1010,
bottom: 1010,
left: 1000,
width: 10,
height: 10
assertPosition( { element, target, positions: [ attachLeft ] }, {
top: -900,
left: -920,
name: 'left'
} );
} );
assertPosition( { element, target, positions: [ attachLeft ] }, {
top: -900,
left: -920,
name: 'left'
it( 'should return coordinates (scroll and border)', () => {
stubWindow( {
innerWidth: 10000,
innerHeight: 10000,
scrollX: 1000,
scrollY: 1000
} );
parent = getElement( {
top: 0,
right: 10,
bottom: 10,
left: 0,
width: 10,
height: 10,
scrollTop: 100,
scrollLeft: 200
}, {
position: 'absolute',
borderLeftWidth: '20px',
borderTopWidth: '40px',
} );
element.parentElement = parent;
assertPosition( { element, target, positions: [ attachLeft ] }, {
top: 160,
left: 260,
name: 'left'
} );
} );

@@ -259,3 +278,3 @@ } );

it( 'should return the very first coordinates if limiter does not fit into the viewport', () => {
stubElementRect( limiter, {
limiter = getElement( {
top: -100,

@@ -328,10 +347,39 @@ right: -80,

function stubElementRect( element, rect ) {
if ( element.getBoundingClientRect.restore ) {
element.getBoundingClientRect.restore();
// Returns a synthetic element.
//
// @private
// @param {Object} properties A set of properties for the element.
// @param {Object} styles A set of styles in `window.getComputedStyle()` format.
function getElement( properties = {}, styles = {} ) {
const element = {
tagName: 'div',
scrollLeft: 0,
scrollTop: 0
};
Object.assign( element, properties );
if ( !styles.borderLeftWidth ) {
styles.borderLeftWidth = '0px';
}
testUtils.sinon.stub( element, 'getBoundingClientRect' ).returns( rect );
if ( !styles.borderTopWidth ) {
styles.borderTopWidth = '0px';
}
global.window.getComputedStyle.withArgs( element ).returns( styles );
return element;
}
// Stubs the window.
//
// @private
// @param {Object} properties A set of properties the window should have.
function stubWindow( properties ) {
global.window = Object.assign( {
getComputedStyle: sinon.stub()
}, properties );
}
// <-- 100px ->

@@ -352,6 +400,3 @@ //

function setElementTargetPlayground() {
element = document.createElement( 'div' );
target = document.createElement( 'div' );
stubElementRect( element, {
element = getElement( {
top: 0,

@@ -365,3 +410,3 @@ right: 20,

stubElementRect( target, {
target = getElement( {
top: 100,

@@ -398,7 +443,3 @@ right: 110,

function setElementTargetLimiterPlayground() {
element = document.createElement( 'div' );
target = document.createElement( 'div' );
limiter = document.createElement( 'div' );
stubElementRect( element, {
element = getElement( {
top: 0,

@@ -412,3 +453,3 @@ right: 20,

stubElementRect( limiter, {
limiter = getElement( {
top: 100,

@@ -422,3 +463,3 @@ right: 10,

stubElementRect( target, {
target = getElement( {
top: 100,

@@ -425,0 +466,0 @@ right: 10,

@@ -11,15 +11,15 @@ /**

it( 'should return 0th item', () => {
expect( nth( 0, getIterator() ) ).to.equal( 11 );
expect( nth( 0, getGenerator() ) ).to.equal( 11 );
} );
it( 'should return the last item', () => {
expect( nth( 2, getIterator() ) ).to.equal( 33 );
expect( nth( 2, getGenerator() ) ).to.equal( 33 );
} );
it( 'should return null if out of range (bottom)', () => {
expect( nth( -1, getIterator() ) ).to.be.null;
expect( nth( -1, getGenerator() ) ).to.be.null;
} );
it( 'should return null if out of range (top)', () => {
expect( nth( 3, getIterator() ) ).to.be.null;
expect( nth( 3, getGenerator() ) ).to.be.null;
} );

@@ -31,3 +31,20 @@

function *getIterator() {
it( 'should consume the given generator', () => {
const generator = getGenerator();
nth( 0, generator );
expect( generator.next().done ).to.equal( true );
} );
it( 'should stop inside the given iterator', () => {
const collection = [ 11, 22, 33 ];
const iterator = collection[ Symbol.iterator ]();
nth( 0, iterator );
expect( iterator.next().value ).to.equal( 22 );
} );
function *getGenerator() {
yield 11;

@@ -34,0 +51,0 @@ yield 22;

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc