Sfoglia il codice sorgente

Allowed event rename in Emitter#delegate().

Aleksander Nowodzinski 9 anni fa
parent
commit
371a5d84b1

+ 11 - 8
packages/ckeditor5-utils/src/emittermixin.js

@@ -277,8 +277,12 @@ const EmitterMixin = {
 			const destinations = this._delegations.get( event );
 
 			if ( destinations ) {
-				for ( let dest of destinations ) {
-					dest.fire( eventInfo, ...args );
+				for ( let [ emitter, name ] of destinations ) {
+					const passedEventInfo = new EventInfo( eventInfo.source, name || eventInfo.name );
+
+					passedEventInfo.path = [ ...eventInfo.path ];
+
+					emitter.fire( passedEventInfo, ...args );
 				}
 			}
 		}
@@ -310,7 +314,7 @@ const EmitterMixin = {
 			 * @method utils.EmitterMixin.delegate#to
 			 * @param {utils.Emitter} emitter An `EmitterMixin` instance which is the destination for delegated events.
 			 */
-			to: ( emitter ) => {
+			to: ( emitter, name ) => {
 				if ( !this._delegations ) {
 					this._delegations = new Map();
 				}
@@ -319,9 +323,9 @@ const EmitterMixin = {
 					let destinations = this._delegations.get( eventName );
 
 					if ( !destinations ) {
-						this._delegations.set( eventName, [ emitter ] );
+						this._delegations.set( eventName, new Map( [ [ emitter, name ] ] ) );
 					} else {
-						destinations.push( emitter );
+						destinations.set( emitter, name );
 					}
 				}
 			}
@@ -351,10 +355,9 @@ const EmitterMixin = {
 			this._delegations.delete( event );
 		} else {
 			const destinations = this._delegations.get( event );
-			const index = destinations.indexOf( emitter );
 
-			if ( index !== -1 ) {
-				destinations.splice( index, 1 );
+			if ( destinations ) {
+				destinations.delete( emitter );
 			}
 		}
 	}

+ 3 - 0
packages/ckeditor5-utils/src/eventinfo.js

@@ -16,6 +16,7 @@ export default class EventInfo {
 		/**
 		 * The object that fired the event.
 		 *
+		 * @readonly
 		 * @member utils.EventInfo#source
 		 */
 		this.source = source;
@@ -23,6 +24,7 @@ export default class EventInfo {
 		/**
 		 * The event name.
 		 *
+		 * @readonly
 		 * @member utils.EventInfo#name
 		 */
 		this.name = name;
@@ -30,6 +32,7 @@ export default class EventInfo {
 		/**
 		 * Path this event has followed. See {@link utils.EmitterMixin#delegate}.
 		 *
+		 * @readonly
 		 * @member utils.EventInfo#path
 		 */
 		this.path = [];

+ 123 - 0
packages/ckeditor5-utils/tests/emittermixin.js

@@ -670,6 +670,32 @@ describe( 'delegate', () => {
 			emitterC.fire( 'foo', data );
 		} );
 
+		it( 'preserves path in event delegation', ( done ) => {
+			const data = {};
+			const emitterA = getEmitterInstance();
+			const emitterB = getEmitterInstance();
+			const emitterC = getEmitterInstance();
+			const emitterD = getEmitterInstance();
+
+			emitterB.delegate( 'foo' ).to( emitterA );
+			emitterB.delegate( 'foo' ).to( emitterC );
+			emitterB.delegate( 'foo' ).to( emitterD );
+
+			emitterD.on( 'foo', ( ...args ) => {
+				assertDelegated( args, {
+					expectedSource: emitterB,
+					expectedName: 'foo',
+					expectedPath: [ emitterB, emitterD ],
+					expectedData: [ data ]
+				} );
+
+				done();
+			} );
+
+			emitterB.fire( 'foo', data );
+			emitterC.fire( 'foo', data );
+		} );
+
 		it( 'executes callbacks first, then delegates further', () => {
 			const emitterA = getEmitterInstance();
 			const emitterB = getEmitterInstance();
@@ -685,6 +711,58 @@ describe( 'delegate', () => {
 
 			sinon.assert.callOrder( spyB, spyA );
 		} );
+
+		it( 'supports delegation under a different name', () => {
+			const emitterA = getEmitterInstance();
+			const emitterB = getEmitterInstance();
+			const emitterC = getEmitterInstance();
+			const emitterD = getEmitterInstance();
+			const spyAFoo = sinon.spy();
+			const spyABar = sinon.spy();
+			const spyCBaz = sinon.spy();
+			const spyDFoo = sinon.spy();
+
+			emitterB.delegate( 'foo' ).to( emitterA, 'bar' );
+			emitterB.delegate( 'foo' ).to( emitterC, 'baz' );
+			emitterB.delegate( 'foo' ).to( emitterD );
+
+			emitterA.on( 'foo', spyAFoo );
+			emitterA.on( 'bar', spyABar );
+			emitterC.on( 'baz', spyCBaz );
+			emitterD.on( 'foo', spyDFoo );
+
+			emitterB.fire( 'foo' );
+
+			sinon.assert.calledOnce( spyABar );
+			sinon.assert.calledOnce( spyCBaz );
+			sinon.assert.calledOnce( spyDFoo );
+			sinon.assert.notCalled( spyAFoo );
+		} );
+
+		it( 'preserves path in delegation under a different name', ( done ) => {
+			const data = {};
+			const emitterA = getEmitterInstance();
+			const emitterB = getEmitterInstance();
+			const emitterC = getEmitterInstance();
+			const emitterD = getEmitterInstance();
+
+			emitterB.delegate( 'foo' ).to( emitterA, 'bar' );
+			emitterB.delegate( 'foo' ).to( emitterC, 'baz' );
+			emitterB.delegate( 'foo' ).to( emitterD );
+
+			emitterD.on( 'foo', ( ...args ) => {
+				assertDelegated( args, {
+					expectedSource: emitterB,
+					expectedName: 'foo',
+					expectedPath: [ emitterB, emitterD ],
+					expectedData: [ data ]
+				} );
+
+				done();
+			} );
+
+			emitterB.fire( 'foo', data );
+		} );
 	} );
 } );
 
@@ -773,6 +851,39 @@ describe( 'stopDelegating', () => {
 		sinon.assert.callOrder( spyFooB, spyFooC, spyFooB );
 	} );
 
+	it( 'stops delegating a specific event under a different name to a specific emitter', () => {
+		const emitterA = getEmitterInstance();
+		const emitterB = getEmitterInstance();
+		const emitterC = getEmitterInstance();
+		const spyFooB = sinon.spy();
+		const spyFooC = sinon.spy();
+
+		emitterA.delegate( 'foo' ).to( emitterB );
+		emitterA.delegate( 'foo' ).to( emitterC, 'bar' );
+
+		emitterB.on( 'foo', spyFooB );
+		emitterC.on( 'bar', spyFooC );
+
+		emitterA.fire( 'foo' );
+
+		sinon.assert.callOrder( spyFooB, spyFooC );
+
+		emitterA.stopDelegating( 'foo', emitterC );
+		emitterA.fire( 'foo' );
+
+		sinon.assert.callOrder( spyFooB, spyFooC, spyFooB );
+	} );
+
+	it( 'passes when stopping delegation of a specific event which has never been delegated', () => {
+		const emitterA = getEmitterInstance();
+		const emitterB = getEmitterInstance();
+
+		expect( () => {
+			emitterA.stopDelegating( 'bar' );
+			emitterA.stopDelegating( 'bar', emitterB );
+		} ).to.not.throw();
+	} );
+
 	it( 'passes when stopping delegation of a specific event to an emitter which wasn\'t a destination', () => {
 		const emitterA = getEmitterInstance();
 		const emitterB = getEmitterInstance();
@@ -784,6 +895,18 @@ describe( 'stopDelegating', () => {
 			emitterA.stopDelegating( 'foo', emitterC );
 		} ).to.not.throw();
 	} );
+
+	it( 'passes when stopping delegation of a specific event to a specific emitter which has never been delegated', () => {
+		const emitterA = getEmitterInstance();
+		const emitterB = getEmitterInstance();
+		const emitterC = getEmitterInstance();
+
+		emitterA.delegate( 'foo' ).to( emitterB );
+
+		expect( () => {
+			emitterA.stopDelegating( 'bar', emitterC );
+		} ).to.not.throw();
+	} );
 } );
 
 function refreshEmitter() {