Przeglądaj źródła

Tests: Asserted bindTo maps.

Aleksander Nowodzinski 8 lat temu
rodzic
commit
7de9cdab6a

+ 11 - 9
packages/ckeditor5-utils/src/collection.js

@@ -432,21 +432,23 @@ export default class Collection {
 		// @private
 		const addItem = ( evt, externalItem, index ) => {
 			const isExternalBoundToThis = externalCollection._bindToCollection == this;
-			const externalItemBoundTo = externalCollection._bindToInternalToExternalMap.get( externalItem );
-			const item = externalItemBoundTo || factory( externalItem );
-
-			this._bindToExternalToInternalMap.set( externalItem, item );
-			this._bindToInternalToExternalMap.set( item, externalItem );
+			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 && externalItemBoundTo ) {
-				return;
-			}
+			if ( isExternalBoundToThis && externalItemBound ) {
+				this._bindToExternalToInternalMap.set( externalItem, externalItemBound );
+				this._bindToInternalToExternalMap.set( externalItemBound, externalItem );
+			} else {
+				const item = factory( externalItem );
 
-			this.add( item, index );
+				this._bindToExternalToInternalMap.set( externalItem, item );
+				this._bindToInternalToExternalMap.set( item, externalItem );
+
+				this.add( item, index );
+			}
 		};
 
 		// Load the initial content of the collection.

+ 109 - 59
packages/ckeditor5-utils/tests/collection.js

@@ -517,8 +517,24 @@ describe( 'Collection', () => {
 			}
 		}
 
-		function isFactoryClass( item ) {
-			return item instanceof FactoryClass;
+		function assertItems( collection, expectedItems, expectedMaps ) {
+			expect( collection.map( i => i.v ) ).to.deep.equal( expectedItems );
+
+			assertMaps( collection, expectedMaps );
+		}
+
+		function assertMaps( collection, expected ) {
+			const ext2Int = collection._bindToExternalToInternalMap;
+			const int2Ext = collection._bindToInternalToExternalMap;
+
+			expect( [ ...ext2Int ] ).to.have.length( expected.length );
+			expect( [ ...int2Ext ] ).to.have.length( expected.length );
+			expect( [ ...ext2Int.entries() ]
+				.map( ( [ key, val ] ) => [ key.v, val.v ] ) )
+				.to.deep.equal( expected );
+			expect( [ ...int2Ext.entries() ]
+				.map( ( [ key, val ] ) => [ val.v, key.v ] ) )
+				.to.deep.equal( expected );
 		}
 
 		it( 'throws when binding more than once', () => {
@@ -751,7 +767,7 @@ describe( 'Collection', () => {
 		} );
 
 		describe( 'two–way data binding', () => {
-			it( 'works with custom factories', () => {
+			it( 'works with custom factories (1)', () => {
 				const collectionA = new Collection();
 				const collectionB = new Collection();
 
@@ -762,26 +778,57 @@ describe( 'Collection', () => {
 				collectionB.on( 'add', spyB );
 
 				// A<--->B
-				collectionA.bindTo( collectionB ).using( 'data' );
-				collectionB.bindTo( collectionA ).as( FactoryClass );
+				collectionA.bindTo( collectionB ).using( i => ( { v: i.v * 2 } ) );
+				collectionB.bindTo( collectionA ).using( i => ( { v: i.v / 2 } ) );
+
+				assertItems( collectionA, [], [] );
+				assertItems( collectionB, [], [] );
 
-				collectionA.add( { value: 'foo' } );
-				collectionA.add( { value: 'bar' } );
+				collectionA.add( { v: 4 } );
+				collectionA.add( { v: 6 } );
 
-				expect( collectionA.map( i => i.value ) ).to.deep.equal( [ 'foo', 'bar' ], 'CollectionA' );
-				expect( collectionB.map( i => i ).every( isFactoryClass ) ).to.be.true;
-				expect( collectionB.map( i => i.data.value ) ).to.deep.equal( [ 'foo', 'bar' ], 'CollectionB' );
+				assertItems( collectionA, [ 4, 6 ], [ [ 2, 4 ], [ 3, 6 ] ] );
+				assertItems( collectionB, [ 2, 3 ], [ [ 4, 2 ], [ 6, 3 ] ] );
 
-				collectionB.add( new FactoryClass( { value: 'baz' } ) );
+				collectionB.add( { v: 4 } );
 
-				expect( collectionA.map( i => i.value ) ).to.deep.equal( [ 'foo', 'bar', 'baz' ], 'CollectionA' );
-				expect( collectionB.map( i => i ).every( isFactoryClass ) ).to.be.true;
-				expect( collectionB.map( i => i.data.value ) ).to.deep.equal( [ 'foo', 'bar', 'baz' ], 'CollectionB' );
+				assertItems( collectionA, [ 4, 6, 8 ], [ [ 2, 4 ], [ 3, 6 ], [ 4, 8 ] ] );
+				assertItems( collectionB, [ 2, 3, 4 ], [ [ 4, 2 ], [ 6, 3 ], [ 8, 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( i => i.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 1:1 binding', () => {
 				const collectionA = new Collection();
 				const collectionB = new Collection();
@@ -796,18 +843,21 @@ describe( 'Collection', () => {
 				collectionA.bindTo( collectionB ).using( i => i );
 				collectionB.bindTo( collectionA ).using( i => i );
 
-				collectionA.add( new FactoryClass( 'foo' ) );
-				collectionA.add( new FactoryClass( 'bar' ) );
+				assertItems( collectionA, [], [] );
+				assertItems( collectionB, [], [] );
 
-				expect( collectionA.map( i => i.data ) ).to.deep.equal( [ 'foo', 'bar' ], 'CollectionA' );
-				expect( collectionB.map( i => i ).every( isFactoryClass ) ).to.be.true;
-				expect( collectionB.map( i => i.data ) ).to.deep.equal( [ 'foo', 'bar' ], 'CollectionB' );
+				collectionA.add( { v: 4 } );
+				collectionA.add( { v: 6 } );
 
-				collectionB.add( new FactoryClass( 'baz' ) );
+				assertItems( collectionA, [ 4, 6 ], [ [ 4, 4 ], [ 6, 6 ] ] );
+				assertItems( collectionB, [ 4, 6 ], [ [ 4, 4 ], [ 6, 6 ] ] );
 
-				expect( collectionA.map( i => i.data ) ).to.deep.equal( [ 'foo', 'bar', 'baz' ], 'CollectionA' );
-				expect( collectionB.map( i => i ).every( isFactoryClass ) ).to.be.true;
-				expect( collectionB.map( i => i.data ) ).to.deep.equal( [ 'foo', 'bar', 'baz' ], 'CollectionB' );
+				collectionB.add( { v: 8 } );
+
+				assertItems( collectionA, [ 4, 6, 8 ], [ [ 4, 4 ], [ 6, 6 ], [ 8, 8 ] ] );
+				assertItems( collectionB, [ 4, 6, 8 ], [ [ 4, 4 ], [ 6, 6 ], [ 8, 8 ] ] );
+
+				expect( collectionA ).to.deep.equal( collectionB );
 
 				sinon.assert.callCount( spyA, 3 );
 				sinon.assert.callCount( spyB, 3 );
@@ -827,31 +877,32 @@ describe( 'Collection', () => {
 				collectionC.on( 'add', spyC );
 
 				// A<--->B--->C
-				collectionA.bindTo( collectionB ).using( i => i );
-				collectionB.bindTo( collectionA ).using( i => i );
-				collectionC.bindTo( collectionB ).using( i => i );
+				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( new FactoryClass( 'foo' ) );
-				collectionA.add( new FactoryClass( 'bar' ) );
+				collectionA.add( { v: 4 } );
+				collectionA.add( { v: 6 } );
 
-				expect( collectionA.map( i => i.data ) ).to.deep.equal( [ 'foo', 'bar' ], 'CollectionA' );
-				expect( collectionB.map( i => i ).every( isFactoryClass ) ).to.be.true;
-				expect( collectionB.map( i => i.data ) ).to.deep.equal( [ 'foo', 'bar' ], 'CollectionB' );
-				expect( collectionC.map( i => i.data ) ).to.deep.equal( [ 'foo', 'bar' ], 'CollectionC' );
+				assertItems( collectionA, [ 4, 6 ], [ [ 2, 4 ], [ 3, 6 ] ] );
+				assertItems( collectionB, [ 2, 3 ], [ [ 4, 2 ], [ 6, 3 ] ] );
+				assertItems( collectionC, [ -2, -3 ], [ [ 2, -2 ], [ 3, -3 ] ] );
 
-				collectionB.add( new FactoryClass( 'baz' ) );
+				collectionB.add( { v: 4 } );
 
-				expect( collectionA.map( i => i.data ) ).to.deep.equal( [ 'foo', 'bar', 'baz' ], 'CollectionA' );
-				expect( collectionB.map( i => i ).every( isFactoryClass ) ).to.be.true;
-				expect( collectionB.map( i => i.data ) ).to.deep.equal( [ 'foo', 'bar', 'baz' ], 'CollectionB' );
-				expect( collectionC.map( i => i.data ) ).to.deep.equal( [ 'foo', 'bar', 'baz' ], 'CollectionC' );
+				assertItems( collectionA, [ 4, 6, 8 ], [ [ 2, 4 ], [ 3, 6 ], [ 4, 8 ] ] );
+				assertItems( collectionB, [ 2, 3, 4 ], [ [ 4, 2 ], [ 6, 3 ], [ 8, 4 ] ] );
+				assertItems( collectionC, [ -2, -3, -4 ], [ [ 2, -2 ], [ 3, -3 ], [ 4, -4 ] ] );
 
-				collectionC.add( new FactoryClass( 'qux' ) );
+				collectionC.add( { v: -1000 } );
 
-				expect( collectionA.map( i => i.data ) ).to.deep.equal( [ 'foo', 'bar', 'baz' ], 'CollectionA' );
-				expect( collectionB.map( i => i ).every( isFactoryClass ) ).to.be.true;
-				expect( collectionB.map( i => i.data ) ).to.deep.equal( [ 'foo', 'bar', 'baz' ], 'CollectionB' );
-				expect( collectionC.map( i => i.data ) ).to.deep.equal( [ 'foo', 'bar', 'baz', 'qux' ], 'CollectionC' );
+				assertItems( collectionA, [ 4, 6, 8 ], [ [ 2, 4 ], [ 3, 6 ], [ 4, 8 ] ] );
+				assertItems( collectionB, [ 2, 3, 4 ], [ [ 4, 2 ], [ 6, 3 ], [ 8, 4 ] ] );
+				assertItems( collectionC, [ -2, -3, -4, -1000 ], [ [ 2, -2 ], [ 3, -3 ], [ 4, -4 ] ] );
 
 				sinon.assert.callCount( spyA, 3 );
 				sinon.assert.callCount( spyB, 3 );
@@ -873,27 +924,27 @@ describe( 'Collection', () => {
 				collectionB.on( 'remove', spyRemoveB );
 
 				// A<--->B
-				collectionA.bindTo( collectionB ).using( 'data' );
-				collectionB.bindTo( collectionA ).as( FactoryClass );
+				collectionA.bindTo( collectionB ).using( i => ( { v: i.v * 2 } ) );
+				collectionB.bindTo( collectionA ).using( i => ( { v: i.v / 2 } ) );
+
+				assertItems( collectionA, [], [] );
+				assertItems( collectionB, [], [] );
 
-				collectionA.add( { value: 'foo' } );
-				collectionA.add( { value: 'bar' } );
+				collectionA.add( { v: 4 } );
+				collectionA.add( { v: 6 } );
 
-				expect( collectionA.map( i => i.value ) ).to.deep.equal( [ 'foo', 'bar' ], 'CollectionA' );
-				expect( collectionB.map( i => i ).every( isFactoryClass ) ).to.be.true;
-				expect( collectionB.map( i => i.data.value ) ).to.deep.equal( [ 'foo', 'bar' ], 'CollectionB' );
+				assertItems( collectionA, [ 4, 6 ], [ [ 2, 4 ], [ 3, 6 ] ] );
+				assertItems( collectionB, [ 2, 3 ], [ [ 4, 2 ], [ 6, 3 ] ] );
 
-				collectionB.add( new FactoryClass( { value: 'baz' } ) );
+				collectionB.add( { v: 4 } );
 
-				expect( collectionA.map( i => i.value ) ).to.deep.equal( [ 'foo', 'bar', 'baz' ], 'CollectionA' );
-				expect( collectionB.map( i => i ).every( isFactoryClass ) ).to.be.true;
-				expect( collectionB.map( i => i.data.value ) ).to.deep.equal( [ 'foo', 'bar', 'baz' ], 'CollectionB' );
+				assertItems( collectionA, [ 4, 6, 8 ], [ [ 2, 4 ], [ 3, 6 ], [ 4, 8 ] ] );
+				assertItems( collectionB, [ 2, 3, 4 ], [ [ 4, 2 ], [ 6, 3 ], [ 8, 4 ] ] );
 
 				collectionB.remove( 0 );
 
-				expect( collectionA.map( i => i.value ) ).to.deep.equal( [ 'bar', 'baz' ], 'CollectionA' );
-				expect( collectionB.map( i => i ).every( isFactoryClass ) ).to.be.true;
-				expect( collectionB.map( i => i.data.value ) ).to.deep.equal( [ 'bar', 'baz' ], 'CollectionB' );
+				assertItems( collectionA, [ 6, 8 ], [ [ 3, 6 ], [ 4, 8 ] ] );
+				assertItems( collectionB, [ 3, 4 ], [ [ 6, 3 ], [ 8, 4 ] ] );
 
 				sinon.assert.callCount( spyAddA, 3 );
 				sinon.assert.callCount( spyAddB, 3 );
@@ -902,9 +953,8 @@ describe( 'Collection', () => {
 
 				collectionA.remove( 1 );
 
-				expect( collectionA.map( i => i.value ) ).to.deep.equal( [ 'bar' ], 'CollectionA' );
-				expect( collectionB.map( i => i ).every( isFactoryClass ) ).to.be.true;
-				expect( collectionB.map( i => i.data.value ) ).to.deep.equal( [ 'bar' ], 'CollectionB' );
+				assertItems( collectionA, [ 6 ], [ [ 3, 6 ] ] );
+				assertItems( collectionB, [ 3 ], [ [ 6, 3 ] ] );
 
 				sinon.assert.callCount( spyAddA, 3 );
 				sinon.assert.callCount( spyAddB, 3 );