From 4f2d716631d20dacc2d42c684f340f25ebefe63b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksander=20Nowodzi=C5=84ski?= Date: Fri, 4 Nov 2016 15:46:48 +0100 Subject: [PATCH] Post rebase fixes. --- src/controllercollection.js | 307 ----------------- tests/controller.js | 565 ------------------------------ tests/controllercollection.js | 629 ---------------------------------- tests/region.js | 4 +- tests/view.js | 4 +- tests/viewcollection.js | 20 +- 6 files changed, 14 insertions(+), 1515 deletions(-) delete mode 100644 src/controllercollection.js delete mode 100644 tests/controller.js delete mode 100644 tests/controllercollection.js diff --git a/src/controllercollection.js b/src/controllercollection.js deleted file mode 100644 index e868a91e..00000000 --- a/src/controllercollection.js +++ /dev/null @@ -1,307 +0,0 @@ -/** - * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -import Collection from '../utils/collection.js'; -import CKEditorError from '../utils/ckeditorerror.js'; - -/** - * Manages UI Controllers. - * - * @memberOf ui - * @extends utils.Collection - */ -export default class ControllerCollection extends Collection { - /** - * Creates an instance of the controller collection, initializing it with a name: - * - * const collection = new ControllerCollection( 'foo' ); - * collection.add( someController ); - * - * **Note**: If needed, controller collection can stay in sync with a collection of models to - * manage list–like components. See {@link ui.ControllerCollection#bind} to learn more. - * - * @param {String} name Name of the collection. - * @param {utils.Locale} [locale] The {@link core.editor.Editor#locale editor's locale} instance. - */ - constructor( name, locale ) { - super(); - - if ( !name ) { - /** - * ControllerCollection must be initialized with a name. - * - * @error ui-controllercollection-no-name - */ - throw new CKEditorError( 'ui-controllercollection-no-name: ControllerCollection must be initialized with a name.' ); - } - - /** - * Name of this collection. - * - * @member {String} ui.ControllerCollection#name - */ - this.name = name; - - /** - * See {@link ui.View#locale} - * - * @readonly - * @member {utils.Locale} ui.ControllerCollection#locale - */ - this.locale = locale; - - /** - * Parent controller of this collection. - * - * @member {ui.Controller} ui.ControllerCollection#parent - */ - this.parent = null; - } - - /** - * Adds a child controller to the collection. If {@link ui.ControllerCollection#parent} {@link ui.Controller} - * instance is ready, the child view is initialized when added. - * - * @param {ui.Controller} controller A child controller. - * @param {Number} [index] Index at which the child will be added to the collection. - * @returns {Promise} A Promise resolved when the child {@link ui.Controller#init} is done. - */ - add( controller, index ) { - super.add( controller, index ); - - // ChildController.init() returns Promise. - let promise = Promise.resolve(); - - if ( this.parent && this.parent.ready && !controller.ready ) { - promise = promise.then( () => { - return controller.init(); - } ); - } - - return promise; - } - - /** - * Synchronizes controller collection with a {@link utils.Collection} of {@link ui.Model} instances. - * The entire collection of controllers reflects changes to the collection of the models, working as a factory. - * - * This method helps bringing complex list–like UI components to life out of the data (like models). The process - * can be automatic: - * - * // This collection stores models. - * const models = new Collection( { idProperty: 'label' } ); - * - * // This controller collection will become a factory for the collection of models. - * const controllers = new ControllerCollection( 'foo', locale ); - * - * // Activate the binding – since now, this controller collection works like a **factory**. - * controllers.bind( models ).as( FooController, FooView ); - * - * // As new models arrive to the collection, each becomes an instance of FooController (FooView) - * // in the controller collection. - * models.add( new Model( { label: 'foo' } ) ); - * models.add( new Model( { label: 'bar' } ) ); - * - * console.log( controllers.length == 2 ); - * - * // Controller collection is updated as the model is removed. - * models.remove( 0 ); - * console.log( controllers.length == 1 ); - * - * or the factory can be driven by a custom callback: - * - * // This collection stores any kind of data. - * const data = new Collection(); - * - * // This controller collection will become a custom factory for the data. - * const controllers = new ControllerCollection( 'foo', locale ); - * - * // Activate the binding – the **factory** is driven by a custom callback. - * controllers.bind( data ).as( ( item, locale ) => { - * if ( !item.foo ) { - * return null; - * } else if ( item.foo == 'bar' ) { - * return new BarController( ..., BarView( locale ) ); - * } else { - * return new DifferentController( ..., DifferentView( locale ) ); - * } - * } ); - * - * // As new data arrive to the collection, each is handled individually by the callback. - * // This will produce BarController. - * data.add( { foo: 'bar' } ); - * - * // 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. - * data.remove( 0 ); - * console.log( controllers.length == 1 ); - * - * @param {utils.Collection.} collection Models to be synchronized with this controller collection. - * @returns {Function} The `as` function in the `bind( collection ).as( ... )` chain. - * It activates factory using controller and view classes or uses a custom callback to produce - * controller (view) instances. - * @returns {Function} return.ControllerClassOrFunction Specifies the constructor of the controller to be used or - * a custom callback function which produces controllers. - * @returns {Function} [return.ViewClass] Specifies constructor of the view to be used. If not specified, - * `ControllerClassOrFunction` works as as custom callback function. - */ - bind( collection ) { - const controllerMap = new Map(); - const that = this; - - return { - as: ( ControllerClassOrFunction, ViewClass ) => { - const handler = ViewClass ? - defaultControllerHandler( ControllerClassOrFunction, ViewClass ) - : - genericControllerHandler( ControllerClassOrFunction ); - - for ( let item of collection ) { - handler.add( item ); - } - - // Updated controller collection when a new item is added. - collection.on( 'add', ( evt, item, index ) => { - handler.add( item, index ); - } ); - - // Update controller collection when a item is removed. - collection.on( 'remove', ( evt, item ) => { - handler.remove( item ); - } ); - } - }; - - function genericControllerHandler( createController ) { - return { - add( data, index ) { - const controller = createController( data, that.locale ); - - controllerMap.set( data, controller ); - - if ( controller ) { - that.add( controller, typeof index == 'number' ? recalculateIndex( index ) : undefined ); - } - }, - - remove( data ) { - const controller = controllerMap.get( data ); - - controllerMap.delete( 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 ) ); - }, controllerMap ); - } - } - - /** - * Delegates selected events coming from within controller models in the collection to desired - * {@link utils.EmitterMixin}. For instance: - * - * const modelA = new Model(); - * const modelB = new Model(); - * const modelC = new Model(); - * - * const controllers = new ControllerCollection( 'name' ); - * - * controllers.delegate( 'eventX' ).to( modelB ); - * controllers.delegate( 'eventX', 'eventY' ).to( modelC ); - * - * controllers.add( new Controller( modelA, ... ) ); - * - * then `eventX` is delegated (fired by) `modelB` and `modelC` along with `customData`: - * - * modelA.fire( 'eventX', customData ); - * - * and `eventY` is delegated (fired by) `modelC` along with `customData`: - * - * modelA.fire( 'eventY', customData ); - * - * See {@link utils.EmitterMixin#delegate}. - * - * @param {...String} events {@link ui.Controller#model} event names to be delegated to another {@link utils.EmitterMixin}. - * @returns {ui.ControllerCollection#delegate#to} - */ - delegate( ...events ) { - if ( !events.length || !isStringArray( events ) ) { - /** - * All event names must be strings. - * - * @error ui-controllercollection-delegate-wrong-events - */ - throw new CKEditorError( 'ui-controllercollection-delegate-wrong-events: All event names must be strings.' ); - } - - return { - /** - * Selects destination for {@link utils.EmitterMixin#delegate} events. - * - * @method ui.ControllerCollection.delegate#to - * @param {utils.EmitterMixin} dest An `EmitterMixin` instance which is the destination for delegated events. - */ - to: ( dest ) => { - // Activate delegating on existing controllers in this collection. - for ( let controller of this ) { - for ( let evtName of events ) { - controller.model.delegate( evtName ).to( dest ); - } - } - - // Activate delegating on future controllers in this collection. - this.on( 'add', ( evt, controller ) => { - for ( let evtName of events ) { - controller.model.delegate( evtName ).to( dest ); - } - } ); - - // Deactivate delegating when controller is removed from this collection. - this.on( 'remove', ( evt, controller ) => { - for ( let evtName of events ) { - controller.model.stopDelegating( evtName, dest ); - } - } ); - } - }; - } -} - -// Check if all entries of the array are of `String` type. -// -// @private -// @param {Array} arr An array to be checked. -// @returns {Boolean} -function isStringArray( arr ) { - return arr.every( a => typeof a == 'string' ); -} diff --git a/tests/controller.js b/tests/controller.js deleted file mode 100644 index 11264570..00000000 --- a/tests/controller.js +++ /dev/null @@ -1,565 +0,0 @@ -/** - * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/* globals document */ -/* bender-tags: ui */ - -import testUtils from 'tests/core/_utils/utils.js'; -import View from 'ckeditor5/ui/view.js'; -import Controller from 'ckeditor5/ui/controller.js'; -import ControllerCollection from 'ckeditor5/ui/controllercollection.js'; -import CKEditorError from 'ckeditor5/utils/ckeditorerror.js'; -import Model from 'ckeditor5/ui/model.js'; -import EventInfo from 'ckeditor5/utils/eventinfo.js'; - -let ParentController, ParentView; - -testUtils.createSinonSandbox(); - -describe( 'Controller', () => { - describe( 'constructor()', () => { - it( 'defines basic properties', () => { - const controller = new Controller(); - - expect( controller.model ).to.be.null; - expect( controller.ready ).to.be.false; - expect( controller.view ).to.be.null; - - expect( controller.collections.length ).to.equal( 1 ); - expect( controller.collections.get( '$anonymous' ) ).to.be.instanceOf( ControllerCollection ); - } ); - - it( 'should accept model and view', () => { - const model = new Model(); - const view = new View(); - const controller = new Controller( model, view ); - - expect( controller.model ).to.equal( model ); - expect( controller.view ).to.equal( view ); - } ); - } ); - - describe( 'init', () => { - it( 'should throw when already initialized', () => { - const controller = new Controller(); - - return controller.init() - .then( () => { - controller.init(); - - throw new Error( 'This should not be executed.' ); - } ) - .catch( ( err ) => { - expect( err ).to.be.instanceof( CKEditorError ); - expect( err.message ).to.match( /ui-controller-init-re/ ); - } ); - } ); - - it( 'should set #ready flag and fire #ready event', () => { - const controller = new Controller(); - const spy = sinon.spy( () => { - expect( controller ).to.have.property( 'ready', true ); - } ); - - controller.on( 'ready', spy ); - - return controller.init().then( () => { - expect( spy.calledOnce ).to.be.true; - } ); - } ); - - it( 'should initialize own view', () => { - const view = new View(); - const controller = new Controller( null, view ); - const spy = testUtils.sinon.spy( view, 'init' ); - - return controller.init().then( () => { - sinon.assert.calledOnce( spy ); - } ); - } ); - - it( 'should initialize child controllers in own collections', () => { - const parentController = new Controller(); - const buttonCollection = new ControllerCollection( 'buttons' ); - parentController.collections.add( buttonCollection ); - - const childController1 = new Controller(); - const childController2 = new Controller(); - const spy1 = testUtils.sinon.spy( childController1, 'init' ); - const spy2 = testUtils.sinon.spy( childController2, 'init' ); - - buttonCollection.add( childController1 ); - buttonCollection.add( childController2 ); - - return parentController.init().then( () => { - expect( buttonCollection.get( 0 ) ).to.equal( childController1 ); - expect( buttonCollection.get( 1 ) ).to.equal( childController2 ); - - sinon.assert.calledOnce( spy1 ); - sinon.assert.calledOnce( spy2 ); - } ); - } ); - - it( 'should initialize child controllers in anonymous collection', () => { - const parentController = new Controller( null, new View() ); - const child1 = new Controller( null, new View() ); - const child2 = new Controller( null, new View() ); - - const spy1 = testUtils.sinon.spy( child1, 'init' ); - const spy2 = testUtils.sinon.spy( child2, 'init' ); - - const collection = parentController.collections.get( '$anonymous' ); - - parentController.add( child1 ); - parentController.add( child2 ); - - expect( collection ).to.have.length( 2 ); - expect( collection.get( 0 ) ).to.equal( child1 ); - expect( collection.get( 1 ) ).to.equal( child2 ); - - return parentController.init().then( () => { - expect( collection.get( 0 ) ).to.equal( child1 ); - expect( collection.get( 1 ) ).to.equal( child2 ); - - sinon.assert.calledOnce( spy1 ); - sinon.assert.calledOnce( spy2 ); - } ); - } ); - } ); - - describe( 'add', () => { - beforeEach( defineParentControllerClass ); - - it( 'should return a promise', () => { - const parentController = new ParentController(); - - expect( parentController.add( 'x', new Controller() ) ).to.be.an.instanceof( Promise ); - } ); - - it( 'should add a controller to specific collection', () => { - const parentController = new ParentController(); - const child1 = new Controller(); - const child2 = new Controller(); - const collection = parentController.collections.get( 'x' ); - - parentController.add( 'x', child1 ); - parentController.add( 'x', child2 ); - - expect( collection ).to.have.length( 2 ); - expect( collection.get( 0 ) ).to.equal( child1 ); - expect( collection.get( 1 ) ).to.equal( child2 ); - } ); - - it( 'should add a controller at specific index', () => { - const parentController = new ParentController(); - const child1 = new Controller(); - const child2 = new Controller(); - const collection = parentController.collections.get( 'x' ); - - parentController.add( 'x', child1 ); - parentController.add( 'x', child2, 0 ); - - expect( collection ).to.have.length( 2 ); - expect( collection.get( 0 ) ).to.equal( child2 ); - expect( collection.get( 1 ) ).to.equal( child1 ); - } ); - - it( 'should add a controller to the anonymous collection', () => { - const parentController = new ParentController( null, new View() ); - const child1 = new Controller( null, new View() ); - const child2 = new Controller( null, new View() ); - - const collection = parentController.collections.get( '$anonymous' ); - - parentController.add( child1 ); - parentController.add( child2 ); - - expect( collection ).to.have.length( 2 ); - expect( collection.get( 0 ) ).to.equal( child1 ); - expect( collection.get( 1 ) ).to.equal( child2 ); - } ); - } ); - - describe( 'remove', () => { - beforeEach( defineParentControllerClass ); - - it( 'should remove a controller from specific collection – by instance', () => { - const parentController = new ParentController(); - const child1 = new Controller(); - const child2 = new Controller(); - const child3 = new Controller(); - const collection = parentController.collections.get( 'x' ); - - parentController.add( 'x', child1 ); - parentController.add( 'x', child2 ); - parentController.add( 'x', child3 ); - - const removed = parentController.remove( 'x', child2 ); - - expect( collection ).to.have.length( 2 ); - expect( collection.get( 0 ) ).to.equal( child1 ); - expect( collection.get( 1 ) ).to.equal( child3 ); - expect( removed ).to.equal( child2 ); - } ); - - it( 'should remove a controller from specific collection – by index', () => { - const parentController = new ParentController(); - const child1 = new Controller(); - const child2 = new Controller(); - const child3 = new Controller(); - const collection = parentController.collections.get( 'x' ); - - parentController.add( 'x', child1 ); - parentController.add( 'x', child2 ); - parentController.add( 'x', child3 ); - - const removed = parentController.remove( 'x', 1 ); - - expect( collection ).to.have.length( 2 ); - expect( collection.get( 0 ) ).to.equal( child1 ); - expect( collection.get( 1 ) ).to.equal( child3 ); - expect( removed ).to.equal( child2 ); - } ); - - it( 'should remove a controller from the anonymous collection', () => { - const parentController = new ParentController(); - const child1 = new Controller(); - const child2 = new Controller(); - const child3 = new Controller(); - - parentController.add( child1 ); - parentController.add( child2 ); - parentController.add( child3 ); - - const collection = parentController.collections.get( '$anonymous' ); - const removed = parentController.remove( child2 ); - - expect( collection ).to.have.length( 2 ); - expect( collection.get( 0 ) ).to.equal( child1 ); - expect( collection.get( 1 ) ).to.equal( child3 ); - expect( removed ).to.equal( child2 ); - } ); - } ); - - describe( 'collections', () => { - describe( 'add', () => { - beforeEach( defineParentViewClass ); - beforeEach( defineParentControllerClass ); - - it( 'should add a child controller which has no view', () => { - const parentController = new ParentController( null, new ParentView() ); - const collection = parentController.collections.get( 'x' ); - const childController = new Controller(); - - return parentController.init() - .then( () => { - return collection.add( childController ); - } ) - .then( () => { - expect( collection.get( 0 ) ).to.equal( childController ); - } ); - } ); - - it( 'should append child controller\'s view to parent controller\'s view', () => { - const parentView = new ParentView(); - const parentController = new ParentController( null, parentView ); - const collection = parentController.collections.get( 'x' ); - const childController = new Controller( null, new View() ); - const spy = testUtils.sinon.spy(); - - parentView.regions.get( 'x' ).views.on( 'add', spy ); - - collection.add( childController ); - - sinon.assert.notCalled( spy ); - - collection.remove( childController ); - - return parentController.init() - .then( () => { - return collection.add( childController ); - } ) - .then( () => { - sinon.assert.calledOnce( spy ); - sinon.assert.calledWithExactly( spy, - sinon.match.instanceOf( EventInfo ), childController.view, 0 ); - } ); - } ); - - it( 'should append child controller\'s view to parent controller\'s view at given index', () => { - const parentController = new ParentController( null, new ParentView() ); - const collection = parentController.collections.get( 'x' ); - - const childView1 = new View(); - const childController1 = new Controller( null, childView1 ); - const childView2 = new View(); - const childController2 = new Controller( null, childView2 ); - - return parentController.init() - .then( () => { - return collection.add( childController1 ).then( () => { - return collection.add( childController2, 0 ); - } ); - } ) - .then( () => { - const region = parentController.view.regions.get( 'x' ); - - expect( region.views.get( 0 ) ).to.equal( childView2 ); - expect( region.views.get( 1 ) ).to.equal( childView1 ); - } ); - } ); - - it( 'should not handle views of anonymous collection children', () => { - const parentController = new ParentController( null, new ParentView() ); - - const childView1 = new View(); - const childController1 = new Controller( null, childView1 ); - const childView2 = new View(); - const childController2 = new Controller( null, childView2 ); - - return parentController.init() - .then( () => { - return parentController.add( childController1 ).then( () => { - return parentController.add( childController2 ); - } ); - } ) - .then( () => { - const region = parentController.view.regions.get( 'x' ); - - expect( region.views ).to.have.length( 0 ); - } ); - } ); - } ); - - describe( 'remove', () => { - beforeEach( defineParentViewClass ); - - it( 'should remove child controller\'s view from parent controller\'s view', () => { - const parentView = new ParentView(); - const parentController = new ParentController( null, parentView ); - const collection = parentController.collections.get( 'x' ); - const childController = new Controller( null, new View() ); - const spy = testUtils.sinon.spy(); - parentView.regions.get( 'x' ).views.on( 'remove', spy ); - - collection.add( childController ); - - sinon.assert.notCalled( spy ); - - return parentController.init() - .then( () => { - collection.remove( childController ); - sinon.assert.calledOnce( spy ); - sinon.assert.calledWithExactly( spy, - sinon.match.instanceOf( EventInfo ), childController.view ); - } ); - } ); - } ); - } ); - - describe( 'destroy', () => { - beforeEach( defineParentViewClass ); - beforeEach( defineParentControllerClass ); - - it( 'should destroy the controller', () => { - const view = new View(); - const controller = new Controller( null, view ); - const spy = testUtils.sinon.spy( view, 'destroy' ); - - return controller.init() - .then( () => { - return controller.destroy(); - } ) - .then( () => { - sinon.assert.calledOnce( spy ); - - expect( controller.model ).to.be.null; - expect( controller.ready ).to.be.null; - expect( controller.view ).to.be.null; - expect( controller.collections ).to.be.null; - } ); - } ); - - it( 'should destroy the controller which has no view', () => { - const controller = new Controller( null, null ); - - return controller.init() - .then( () => { - return controller.destroy(); - } ) - .then( () => { - expect( controller.model ).to.be.null; - expect( controller.view ).to.be.null; - expect( controller.collections ).to.be.null; - } ); - } ); - - it( 'should destroy child controllers in collections with their views', () => { - const parentController = new ParentController( null, new ParentView() ); - const collection = parentController.collections.get( 'x' ); - const childView = new View(); - const childController = new Controller( null, childView ); - const spy = testUtils.sinon.spy( childView, 'destroy' ); - - collection.add( childController ); - - return parentController.init() - .then( () => { - return parentController.destroy(); - } ) - .then( () => { - sinon.assert.calledOnce( spy ); - expect( childController.model ).to.be.null; - expect( childController.view ).to.be.null; - expect( childController.collections ).to.be.null; - } ); - } ); - - it( 'should destroy child controllers in collections when they have no views', () => { - const parentController = new ParentController( null, new ParentView() ); - const collection = parentController.collections.get( 'x' ); - const childController = new Controller( null, null ); - - collection.add( childController ); - - return parentController.init() - .then( () => { - return parentController.destroy(); - } ) - .then( () => { - expect( childController.model ).to.be.null; - expect( childController.view ).to.be.null; - expect( childController.collections ).to.be.null; - } ); - } ); - - it( 'should destroy child controllers in anonymous collection along with their views', () => { - const parentController = new ParentController( null, new ParentView() ); - const childView = new View(); - const childController = new Controller( null, childView ); - const spy = testUtils.sinon.spy( childView, 'destroy' ); - - parentController.add( childController ); - - return parentController.init() - .then( () => { - return parentController.destroy(); - } ) - .then( () => { - sinon.assert.calledOnce( spy ); - expect( childController.model ).to.be.null; - expect( childController.view ).to.be.null; - expect( childController.collections ).to.be.null; - } ); - } ); - - // See #11 - it( 'should correctly destroy multiple controller collections', () => { - const parentController = new Controller(); - const controllerCollectionCollection = parentController.collections; // Yep... it's correct :D. - const childControllers = []; - const collections = [ 'a', 'b', 'c' ].map( name => { - const collection = new ControllerCollection( name ); - const childController = new Controller(); - - childController.destroy = sinon.spy(); - - parentController.collections.add( collection ); - collection.add( childController ); - childControllers.push( childController ); - - return collection; - } ); - - return parentController.init() - .then( () => { - return parentController.destroy(); - } ) - .then( () => { - expect( controllerCollectionCollection ).to.have.lengthOf( 0, 'parentController.collections is empty' ); - expect( collections.map( collection => collection.length ) ) - .to.deep.equal( [ 0, 0, 0 ], 'all collections are empty' ); - expect( childControllers.map( controller => controller.destroy.calledOnce ) ) - .to.deep.equal( [ true, true, true ], 'all child controllers were destroyed' ); - } ); - } ); - - // See #11 - it( 'should correctly destroy collections with multiple child controllers', () => { - const parentController = new Controller(); - const controllerCollectionCollection = parentController.collections; // Yep... it's correct :D. - const controllerCollection = new ControllerCollection( 'foo' ); - const childControllers = []; - - parentController.collections.add( controllerCollection ); - - for ( let i = 0; i < 3; i++ ) { - const childController = new Controller(); - - childController.destroy = sinon.spy(); - - childControllers.push( childController ); - parentController.add( 'foo', childController ); - } - - return parentController.init() - .then( () => { - return parentController.destroy(); - } ) - .then( () => { - expect( controllerCollectionCollection ).to.have.lengthOf( 0, 'parentController.collections is empty' ); - expect( controllerCollection ).to.have.lengthOf( 0, 'child controller collection is empty' ); - expect( childControllers.map( controller => controller.destroy.calledOnce ) ) - .to.deep.equal( [ true, true, true ], 'all child controllers were destroyed' ); - } ); - } ); - } ); - - describe( 'addCollection', () => { - it( 'should add a new collection', () => { - const controller = new Controller(); - - controller.addCollection( 'foo' ); - - expect( controller.collections ).to.have.length( 2 ); - expect( controller.collections.get( 'foo' ).name ).to.equal( 'foo' ); - } ); - - it( 'should return the collection which has been created (chaining)', () => { - const controller = new Controller(); - const returned = controller.addCollection( 'foo' ); - - expect( returned ).to.be.instanceOf( ControllerCollection ); - } ); - - it( 'should pass locale to controller collection', () => { - const locale = {}; - const view = new View( locale ); - - expect( new Controller( {}, view ).addCollection( 'foo' ).locale ).to.equal( locale ); - } ); - } ); -} ); - -function defineParentViewClass() { - ParentView = class extends View { - constructor() { - super(); - - this.element = document.createElement( 'span' ); - this.register( 'x', true ); - } - }; -} - -function defineParentControllerClass() { - ParentController = class extends Controller { - constructor( ...args ) { - super( ...args ); - - this.addCollection( 'x' ); - } - }; -} diff --git a/tests/controllercollection.js b/tests/controllercollection.js deleted file mode 100644 index 286c9609..00000000 --- a/tests/controllercollection.js +++ /dev/null @@ -1,629 +0,0 @@ -/** - * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ - -/* globals document */ -/* bender-tags: ui */ - -import testUtils from 'tests/core/_utils/utils.js'; -import ControllerCollection from 'ckeditor5/ui/controllercollection.js'; -import Controller from 'ckeditor5/ui/controller.js'; -import Collection from 'ckeditor5/utils/collection.js'; -import Model from 'ckeditor5/ui/model.js'; -import View from 'ckeditor5/ui/view.js'; -import Template from 'ckeditor5/ui/template.js'; -import CKEditorError from 'ckeditor5/utils/ckeditorerror.js'; - -testUtils.createSinonSandbox(); - -let ParentView, ItemController, ItemView; -let models; - -describe( 'ControllerCollection', () => { - beforeEach( () => { - defineParentViewClass(); - defineItemControllerClass(); - defineItemViewClass(); - createModelCollection(); - } ); - - describe( 'constructor()', () => { - it( 'should throw when no name is passed', () => { - expect( () => { - new ControllerCollection(); - } ).to.throw( /^ui-controllercollection-no-name/ ); - } ); - - it( 'accepts locale', () => { - const locale = {}; - const collection = new ControllerCollection( 'foo', locale ); - - expect( collection.locale ).to.equal( locale ); - } ); - } ); - - describe( 'add', () => { - it( 'should add a child controller and return promise', () => { - const parentController = new Controller(); - const childController = new Controller(); - const collection = parentController.addCollection( 'x' ); - - const returned = collection.add( childController ); - - expect( returned ).to.be.an.instanceof( Promise ); - expect( collection.get( 0 ) ).to.be.equal( childController ); - } ); - - it( 'should add a child controller at given position', () => { - const parentController = new Controller(); - const childController1 = new Controller(); - const childController2 = new Controller(); - const collection = parentController.addCollection( 'x' ); - - collection.add( childController1 ); - collection.add( childController2, 0 ); - - expect( collection.get( 0 ) ).to.be.equal( childController2 ); - expect( collection.get( 1 ) ).to.be.equal( childController1 ); - } ); - - it( 'should initialize child controller if parent is ready', () => { - const parentController = new Controller( null, new ParentView() ); - const childController = new Controller( null, new View() ); - const spy = testUtils.sinon.spy( childController, 'init' ); - const collection = parentController.addCollection( 'x' ); - - collection.add( childController ); - collection.remove( childController ); - - sinon.assert.notCalled( spy ); - - return parentController.init() - .then( () => { - return collection.add( childController ); - } ) - .then( () => { - sinon.assert.calledOnce( spy ); - } ); - } ); - - it( 'should not initialize child controller twice', () => { - const parentController = new Controller( null, new ParentView() ); - const childController = new Controller( null, new View() ); - const spy = testUtils.sinon.spy( childController, 'init' ); - const collection = parentController.addCollection( 'x' ); - - return parentController.init() - .then( () => { - return childController.init(); - } ) - .then( () => { - return collection.add( childController ); - } ) - .then( () => { - sinon.assert.calledOnce( spy ); - } ); - } ); - } ); - - describe( 'bind', () => { - it( 'returns object', () => { - expect( new ControllerCollection( 'foo' ).bind( {} ) ).to.be.an( 'object' ); - } ); - - it( 'provides "as" interface', () => { - const bind = new ControllerCollection( 'foo' ).bind( {} ); - - expect( bind ).to.have.keys( 'as' ); - expect( bind.as ).to.be.a( 'function' ); - } ); - - describe( 'as', () => { - it( 'does not chain', () => { - const controllers = new ControllerCollection( 'synced' ); - const returned = controllers.bind( models ).as( ItemController, ItemView ); - - expect( returned ).to.be.undefined; - } ); - - describe( 'standard handler', () => { - it( 'expands the initial collection of the models', () => { - const controllers = new ControllerCollection( 'synced' ); - - controllers.bind( models ).as( ItemController, ItemView ); - - expect( controllers ).to.have.length( 5 ); - expect( controllers.get( 0 ).model.uid ).to.equal( '0' ); - expect( controllers.get( 4 ).model.uid ).to.equal( '4' ); - } ); - - it( 'uses the controller and view classes to expand the collection', () => { - const controllers = new ControllerCollection( 'synced' ); - - controllers.bind( models ).as( ItemController, ItemView ); - - expect( controllers.get( 0 ) ).to.be.instanceOf( ItemController ); - expect( controllers.get( 0 ).view ).to.be.instanceOf( ItemView ); - } ); - - it( 'supports adding new models to the collection', () => { - const controllers = new ControllerCollection( 'synced' ); - - controllers.bind( models ).as( ItemController, ItemView ); - - models.add( new Model( { uid: '6' } ) ); - models.add( new Model( { uid: '5' } ), 5 ); - - expect( controllers.get( 5 ).model.uid ).to.equal( '5' ); - expect( controllers.get( 6 ).model.uid ).to.equal( '6' ); - expect( controllers ).to.have.length( 7 ); - } ); - - it( 'supports adding to the beginning of the collection', () => { - const controllers = new ControllerCollection( 'synced' ); - - controllers.bind( models ).as( ItemController, ItemView ); - - models.add( new Model( { uid: 'x' } ), 0 ); - - expect( controllers.map( c => c.model.uid ) ).to.deep.equal( [ 'x', '0', '1', '2', '3', '4' ] ); - } ); - - it( 'supports removing models from the collection', () => { - const controllers = new ControllerCollection( 'synced' ); - - controllers.bind( models ).as( ItemController, ItemView ); - - models.remove( 2 ); - models.remove( 3 ); - - expect( controllers.map( c => c.model.uid ) ).to.have.members( [ '0', '1', '3' ] ); - } ); - - it( 'passes controller collection\'s locale to the views', () => { - const locale = {}; - const controllers = new ControllerCollection( 'synced', locale ); - - controllers.bind( models ).as( ItemController, ItemView ); - - expect( controllers.get( 0 ).view.locale ).to.equal( locale ); - } ); - } ); - - describe( 'custom handler', () => { - it( 'expands the initial collection of the models', () => { - const controllers = new ControllerCollection( 'synced' ); - - controllers.bind( models ).as( ( model, locale ) => { - return new ItemController( model, new ItemView( locale ) ); - } ); - - expect( controllers ).to.have.length( 5 ); - expect( controllers.get( 0 ).model.uid ).to.equal( '0' ); - expect( controllers.get( 4 ).model.uid ).to.equal( '4' ); - } ); - - it( 'uses the controller and view classes to expand the collection', () => { - const controllers = new ControllerCollection( 'synced' ); - - controllers.bind( models ).as( ( model, locale ) => { - return new ItemController( model, new ItemView( locale ) ); - } ); - - expect( controllers.get( 0 ) ).to.be.instanceOf( ItemController ); - expect( controllers.get( 0 ).view ).to.be.instanceOf( ItemView ); - } ); - - it( 'supports adding new models to the collection', () => { - const controllers = new ControllerCollection( 'synced' ); - - controllers.bind( models ).as( ( model, locale ) => { - return new ItemController( model, new ItemView( locale ) ); - } ); - - models.add( new Model( { uid: '6' } ) ); - models.add( new Model( { uid: '5' } ), 5 ); - - expect( controllers.get( 5 ).model.uid ).to.equal( '5' ); - expect( controllers.get( 6 ).model.uid ).to.equal( '6' ); - expect( controllers ).to.have.length( 7 ); - } ); - - it( 'supports removing models from the collection', () => { - const controllers = new ControllerCollection( 'synced' ); - - controllers.bind( models ).as( ( model, locale ) => { - return new ItemController( model, new ItemView( locale ) ); - } ); - - models.remove( 2 ); - models.remove( 3 ); - - 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 ); - - controllers.bind( models ).as( ( model, locale ) => { - return new ItemController( model, new ItemView( locale ) ); - } ); - - expect( controllers.get( 0 ).view.locale ).to.equal( locale ); - } ); - } ); - - describe( 'custom data format with custom handler', () => { - it( 'expands the initial collection of the models', () => { - const controllers = new ControllerCollection( 'synced' ); - const data = new Collection(); - - data.add( { foo: 'a' } ); - data.add( { foo: 'b' } ); - - controllers.bind( data ).as( ( item, locale ) => { - const model = new Model( { - custom: item.foo - } ); - - return new ItemController( model, new ItemView( locale ) ); - } ); - - expect( controllers ).to.have.length( 2 ); - expect( controllers.get( 0 ).model.custom ).to.equal( 'a' ); - expect( controllers.get( 1 ).model.custom ).to.equal( 'b' ); - expect( controllers.get( 0 ) ).to.be.instanceOf( ItemController ); - expect( controllers.get( 0 ).view ).to.be.instanceOf( ItemView ); - } ); - } ); - } ); - } ); - - describe( 'delegate', () => { - it( 'should throw when event names are not strings', () => { - const collection = new ControllerCollection( 'foo' ); - - expect( () => { - collection.delegate(); - } ).to.throw( CKEditorError, /ui-controllercollection-delegate-wrong-events/ ); - - expect( () => { - collection.delegate( new Date() ); - } ).to.throw( CKEditorError, /ui-controllercollection-delegate-wrong-events/ ); - - expect( () => { - collection.delegate( 'color', new Date() ); - } ).to.throw( CKEditorError, /ui-controllercollection-delegate-wrong-events/ ); - } ); - - it( 'returns object', () => { - expect( new ControllerCollection( 'foo' ).delegate( 'foo' ) ).to.be.an( 'object' ); - } ); - - it( 'provides "to" interface', () => { - const delegate = new ControllerCollection( 'foo' ).delegate( 'foo' ); - - expect( delegate ).to.have.keys( 'to' ); - expect( delegate.to ).to.be.a( 'function' ); - } ); - - describe( 'to', () => { - it( 'does not chain', () => { - const collection = new ControllerCollection( 'foo' ); - const returned = collection.delegate( 'foo' ).to( {} ); - - expect( returned ).to.be.undefined; - } ); - - it( 'forwards an event to another observable – existing controller', ( done ) => { - const target = new Model(); - const collection = new ControllerCollection( 'foo' ); - const model = new Model(); - - collection.add( new Controller( model ) ); - collection.delegate( 'foo' ).to( target ); - - target.on( 'foo', ( ...args ) => { - assertDelegated( args, { - expectedName: 'foo', - expectedSource: model, - expectedPath: [ model, target ], - expectedData: [] - } ); - - done(); - } ); - - model.fire( 'foo' ); - } ); - - it( 'forwards an event to another observable – new controller', ( done ) => { - const target = new Model(); - const collection = new ControllerCollection( 'foo' ); - const model = new Model(); - - collection.delegate( 'foo' ).to( target ); - collection.add( new Controller( model ) ); - - target.on( 'foo', ( ...args ) => { - assertDelegated( args, { - expectedName: 'foo', - expectedSource: model, - expectedPath: [ model, target ], - expectedData: [] - } ); - - done(); - } ); - - model.fire( 'foo' ); - } ); - - it( 'forwards multiple events to another observable', () => { - const target = new Model(); - const collection = new ControllerCollection( 'foo' ); - const modelA = new Model(); - const modelB = new Model(); - const modelC = new Model(); - const spyFoo = sinon.spy(); - const spyBar = sinon.spy(); - const spyBaz = sinon.spy(); - - collection.delegate( 'foo', 'bar', 'baz' ).to( target ); - collection.add( new Controller( modelA ) ); - collection.add( new Controller( modelB ) ); - collection.add( new Controller( modelC ) ); - - target.on( 'foo', spyFoo ); - target.on( 'bar', spyBar ); - target.on( 'baz', spyBaz ); - - modelA.fire( 'foo' ); - - sinon.assert.calledOnce( spyFoo ); - sinon.assert.notCalled( spyBar ); - sinon.assert.notCalled( spyBaz ); - - assertDelegated( spyFoo.args[ 0 ], { - expectedName: 'foo', - expectedSource: modelA, - expectedPath: [ modelA, target ], - expectedData: [] - } ); - - modelB.fire( 'bar' ); - - sinon.assert.calledOnce( spyFoo ); - sinon.assert.calledOnce( spyBar ); - sinon.assert.notCalled( spyBaz ); - - assertDelegated( spyBar.args[ 0 ], { - expectedName: 'bar', - expectedSource: modelB, - expectedPath: [ modelB, target ], - expectedData: [] - } ); - - modelC.fire( 'baz' ); - - sinon.assert.calledOnce( spyFoo ); - sinon.assert.calledOnce( spyBar ); - sinon.assert.calledOnce( spyBaz ); - - assertDelegated( spyBaz.args[ 0 ], { - expectedName: 'baz', - expectedSource: modelC, - expectedPath: [ modelC, target ], - expectedData: [] - } ); - - modelC.fire( 'not-delegated' ); - - sinon.assert.calledOnce( spyFoo ); - sinon.assert.calledOnce( spyBar ); - sinon.assert.calledOnce( spyBaz ); - } ); - - it( 'does not forward events which are not supposed to be delegated', () => { - const target = new Model(); - const collection = new ControllerCollection( 'foo' ); - const model = new Model(); - const spyFoo = sinon.spy(); - const spyBar = sinon.spy(); - const spyBaz = sinon.spy(); - - collection.delegate( 'foo', 'bar', 'baz' ).to( target ); - collection.add( new Controller( model ) ); - - target.on( 'foo', spyFoo ); - target.on( 'bar', spyBar ); - target.on( 'baz', spyBaz ); - - model.fire( 'foo' ); - model.fire( 'bar' ); - model.fire( 'baz' ); - model.fire( 'not-delegated' ); - - sinon.assert.callOrder( spyFoo, spyBar, spyBaz ); - sinon.assert.callCount( spyFoo, 1 ); - sinon.assert.callCount( spyBar, 1 ); - sinon.assert.callCount( spyBaz, 1 ); - } ); - - it( 'stops forwarding when controller removed from the collection', () => { - const target = new Model(); - const collection = new ControllerCollection( 'foo' ); - const model = new Model(); - const spy = sinon.spy(); - - collection.delegate( 'foo' ).to( target ); - target.on( 'foo', spy ); - - collection.add( new Controller( model ) ); - model.fire( 'foo' ); - - sinon.assert.callCount( spy, 1 ); - - collection.remove( 0 ); - model.fire( 'foo' ); - - sinon.assert.callCount( spy, 1 ); - } ); - - it( 'supports deep event delegation', ( done ) => { - const collection = new ControllerCollection( 'foo' ); - const target = new Model(); - const modelA = new Model(); - const modelAA = new Model(); - const data = {}; - - const controllerA = new Controller( modelA ); - const controllerAA = new Controller( modelAA ); - const barCollection = controllerA.addCollection( 'bar' ); - - collection.add( controllerA ); - collection.delegate( 'foo' ).to( target ); - - barCollection.add( controllerAA ); - barCollection.delegate( 'foo' ).to( modelA ); - - target.on( 'foo', ( ...args ) => { - assertDelegated( args, { - expectedName: 'foo', - expectedSource: modelAA, - expectedPath: [ modelAA, modelA, target ], - expectedData: [ data ] - } ); - - done(); - } ); - - modelAA.fire( 'foo', data ); - } ); - } ); - } ); -} ); - -function defineParentViewClass() { - ParentView = class extends View { - constructor() { - super(); - - this.element = document.createElement( 'span' ); - this.register( 'x', true ); - } - }; -} - -function defineItemControllerClass() { - ItemController = class extends Controller { - constructor( model, view ) { - super( model, view ); - - view.bind( 'uid' ).to( model ); - } - }; -} - -function defineItemViewClass() { - ItemView = class extends View { - constructor( locale ) { - super( locale ); - - const bind = this.bindTemplate; - - this.template = new Template( { - tag: 'li', - - attributes: { - id: bind.to( 'uid' ) - } - } ); - } - }; -} - -function createModelCollection() { - models = new Collection( { idProperty: 'uid' } ); - - for ( let i = 0; i < 5; i++ ) { - models.add( new Model( { - uid: Number( i ).toString() - } ) ); - } -} - -function assertDelegated( evtArgs, { expectedName, expectedSource, expectedPath, expectedData } ) { - const evtInfo = evtArgs[ 0 ]; - - expect( evtInfo.name ).to.equal( expectedName ); - expect( evtInfo.source ).to.equal( expectedSource ); - expect( evtInfo.path ).to.deep.equal( expectedPath ); - expect( evtArgs.slice( 1 ) ).to.deep.equal( expectedData ); -} diff --git a/tests/region.js b/tests/region.js index d77eef3d..4e8a79f7 100644 --- a/tests/region.js +++ b/tests/region.js @@ -24,7 +24,7 @@ describe( 'View', () => { } ); } ); - describe( 'init', () => { + describe( 'init()', () => { it( 'accepts region element', () => { region.init( el ); @@ -89,7 +89,7 @@ describe( 'View', () => { } ); } ); - describe( 'destroy', () => { + describe( 'destroy()', () => { it( 'destroys the region', () => { region.init( el ); region.views.add( new TestViewA() ); diff --git a/tests/view.js b/tests/view.js index 3b79d312..5447edc8 100644 --- a/tests/view.js +++ b/tests/view.js @@ -125,7 +125,7 @@ describe( 'View', () => { } ); } ); - describe( 'init', () => { + describe( 'init()', () => { beforeEach( createViewWithChildren ); it( 'should throw if already initialized', () => { @@ -214,7 +214,7 @@ describe( 'View', () => { } ); } ); - describe( 'destroy', () => { + describe( 'destroy()', () => { beforeEach( createViewWithChildren ); it( 'should return a promise', () => { diff --git a/tests/viewcollection.js b/tests/viewcollection.js index 93a608fd..b5e6e782 100644 --- a/tests/viewcollection.js +++ b/tests/viewcollection.js @@ -6,13 +6,13 @@ /* global document */ /* bender-tags: ui */ -import CKEditorError from '/ckeditor5/utils/ckeditorerror.js'; -import Collection from '/ckeditor5/utils/collection.js'; -import testUtils from '/tests/core/_utils/utils.js'; -import View from '/ckeditor5/ui/view.js'; -import ViewCollection from '/ckeditor5/ui/viewcollection.js'; -import Template from '/ckeditor5/ui/template.js'; -import normalizeHtml from '/tests/utils/_utils/normalizehtml.js'; +import CKEditorError from 'ckeditor5/utils/ckeditorerror.js'; +import Collection from 'ckeditor5/utils/collection.js'; +import testUtils from 'tests/core/_utils/utils.js'; +import View from 'ckeditor5/ui/view.js'; +import ViewCollection from 'ckeditor5/ui/viewcollection.js'; +import Template from 'ckeditor5/ui/template.js'; +import normalizeHtml from 'tests/utils/_utils/normalizehtml.js'; let collection; @@ -21,7 +21,7 @@ testUtils.createSinonSandbox(); describe( 'ViewCollection', () => { beforeEach( createTestCollection ); - describe( 'constructor', () => { + describe( 'constructor()', () => { it( 'sets basic properties and attributes', () => { expect( collection.locale ).to.be.undefined; expect( collection.ready ).to.be.false; @@ -71,7 +71,7 @@ describe( 'ViewCollection', () => { } ); } ); - describe( 'init', () => { + describe( 'init()', () => { it( 'should return a promise', () => { expect( collection.init() ).to.be.instanceof( Promise ); } ); @@ -102,7 +102,7 @@ describe( 'ViewCollection', () => { } ); } ); - describe( 'destroy', () => { + describe( 'destroy()', () => { it( 'should return a promise', () => { expect( collection.destroy() ).to.be.instanceof( Promise ); } );