Browse Source

Implement null handling with tests.

Piotr Jasiun 9 years ago
parent
commit
36a5cfb246

+ 27 - 4
packages/ckeditor5-ui/src/controllercollection.js

@@ -120,7 +120,9 @@ export default class ControllerCollection extends Collection {
 	 *
 	 *		// Activate the binding – the **factory** is driven by a custom callback.
 	 *		controllers.bind( data ).as( ( item, locale ) => {
-	 *			if ( item.foo == 'bar' ) {
+	 *			if ( !item.foo ) {
+	 *				return null;
+	 *			} else if ( item.foo == 'bar' ) {
 	 *				return new BarController( ..., BarView( locale ) );
 	 *			} else {
 	 *				return new DifferentController( ..., DifferentView( locale ) );
@@ -134,6 +136,9 @@ export default class ControllerCollection extends Collection {
 	 *		// And this one will become DifferentController.
 	 *		data.add( { foo: 'baz' } );
 	 *
+	 *		// Also there will be no controller for data without property `foo`.
+	 *		data.add( {} );
+	 *
 	 *		console.log( controllers.length == 2 );
 	 *
 	 *		// Controller collection is updated as the data is removed.
@@ -179,11 +184,13 @@ export default class ControllerCollection extends Collection {
 		function genericControllerHandler( createController ) {
 			return {
 				add( data, index ) {
-					const controller = createController( data );
+					const controller = createController( data, that.locale );
 
 					controllerMap.set( data, controller );
 
-					that.add( controller, index );
+					if ( controller ) {
+						that.add( controller, index ? recalculateIndex( index ) : undefined );
+					}
 				},
 
 				remove( data ) {
@@ -191,11 +198,27 @@ export default class ControllerCollection extends Collection {
 
 					controllerMap.delete( controller );
 
-					this.remove( controller );
+					if ( controller ) {
+						that.remove( controller );
+					}
 				}
 			};
 		}
 
+		// Decrement index for each item which has no corresponding controller.
+		function recalculateIndex( index ) {
+			let outputIndex = index;
+
+			for ( let i = 0; i < index; i++ ) {
+				// index -> data -> controller
+				if ( !controllerMap.get( collection.get( i ) ) ) {
+					outputIndex--;
+				}
+			}
+
+			return outputIndex;
+		}
+
 		function defaultControllerHandler( ControllerClass, ViewClass ) {
 			return genericControllerHandler( ( model ) => {
 				return new ControllerClass( model, new ViewClass( that.locale ) );

+ 67 - 3
packages/ckeditor5-ui/tests/controllercollection.js

@@ -127,7 +127,7 @@ describe( 'ControllerCollection', () => {
 				expect( returned ).to.be.undefined;
 			} );
 
-			describe( 'standard factory', () => {
+			describe( 'standard handler', () => {
 				it( 'expands the initial collection of the models', () => {
 					const controllers = new ControllerCollection( 'synced' );
 
@@ -181,7 +181,7 @@ describe( 'ControllerCollection', () => {
 				} );
 			} );
 
-			describe( 'custom factory', () => {
+			describe( 'custom handler', () => {
 				it( 'expands the initial collection of the models', () => {
 					const controllers = new ControllerCollection( 'synced' );
 
@@ -233,6 +233,70 @@ describe( 'ControllerCollection', () => {
 					expect( controllers.map( c => c.model.uid ) ).to.have.members( [ '0', '1', '3' ] );
 				} );
 
+				it( 'supports returning null', () => {
+					const controllers = new ControllerCollection( 'synced' );
+
+					controllers.bind( models ).as( ( model, locale ) => {
+						if ( model.uid == 2 ) {
+							return null;
+						} else {
+							return new ItemController( model, new ItemView( locale ) );
+						}
+					} );
+
+					expect( controllers.map( c => c.model.uid ) ).to.have.members( [ '0', '1', '3', '4' ] );
+				} );
+
+				it( 'supports adding to structure with missing controller', () => {
+					const controllers = new ControllerCollection( 'synced' );
+
+					controllers.bind( models ).as( ( model, locale ) => {
+						if ( model.uid == 2 ) {
+							return null;
+						} else {
+							return new ItemController( model, new ItemView( locale ) );
+						}
+					} );
+
+					models.add( new Model( { uid: '0.5' } ), 1 );
+					models.add( new Model( { uid: '3.5' } ), 5 );
+
+					expect( models.map( c => c.uid ) ).to.have.members( [ '0', '0.5', '1', '2', '3', '3.5', '4' ] );
+					expect( controllers.map( c => c.model.uid ) ).to.have.members( [ '0', '0.5', '1', '3', '3.5', '4' ] );
+				} );
+
+				it( 'supports removing to structure with missing controller', () => {
+					const controllers = new ControllerCollection( 'synced' );
+
+					controllers.bind( models ).as( ( model, locale ) => {
+						if ( model.uid == 2 ) {
+							return null;
+						} else {
+							return new ItemController( model, new ItemView( locale ) );
+						}
+					} );
+
+					models.remove( 3 );
+
+					expect( controllers.map( c => c.model.uid ) ).to.have.members( [ '0', '1', '4' ] );
+				} );
+
+				it( 'supports removing model with missing controller', () => {
+					const controllers = new ControllerCollection( 'synced' );
+
+					controllers.bind( models ).as( ( model, locale ) => {
+						if ( model.uid == 2 ) {
+							return null;
+						} else {
+							return new ItemController( model, new ItemView( locale ) );
+						}
+					} );
+
+					models.remove( 2 );
+
+					expect( controllers.map( c => c.model.uid ) ).to.have.members( [ '0', '1', '3', '4' ] );
+				} );
+
 				it( 'passes controller collection\'s locale to the views', () => {
 					const locale = {};
 					const controllers = new ControllerCollection( 'synced', locale );
@@ -245,7 +309,7 @@ describe( 'ControllerCollection', () => {
 				} );
 			} );
 
-			describe( 'custom data format with custom factory', () => {
+			describe( 'custom data format with custom handler', () => {
 				it( 'expands the initial collection of the models', () => {
 					const controllers = new ControllerCollection( 'synced' );
 					const data = new Collection();