controllercollection.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* bender-tags: ui */
  6. import testUtils from '/tests/ckeditor5/_utils/utils.js';
  7. import ControllerCollection from '/ckeditor5/ui/controllercollection.js';
  8. import Controller from '/ckeditor5/ui/controller.js';
  9. import Collection from '/ckeditor5/utils/collection.js';
  10. import Model from '/ckeditor5/ui/model.js';
  11. import View from '/ckeditor5/ui/view.js';
  12. import Template from '/ckeditor5/ui/template.js';
  13. testUtils.createSinonSandbox();
  14. let ParentView, ItemController, ItemView;
  15. let models;
  16. describe( 'ControllerCollection', () => {
  17. beforeEach( () => {
  18. defineParentViewClass();
  19. defineItemControllerClass();
  20. defineItemViewClass();
  21. createModelCollection();
  22. } );
  23. describe( 'constructor', () => {
  24. it( 'should throw when no name is passed', () => {
  25. expect( () => {
  26. new ControllerCollection();
  27. } ).to.throw( /^ui-controllercollection-no-name/ );
  28. } );
  29. it( 'accepts locale', () => {
  30. const locale = {};
  31. const collection = new ControllerCollection( 'foo', locale );
  32. expect( collection.locale ).to.equal( locale );
  33. } );
  34. } );
  35. describe( 'add', () => {
  36. it( 'should add a child controller and return promise', () => {
  37. const parentController = new Controller();
  38. const childController = new Controller();
  39. const collection = new ControllerCollection( 'x' );
  40. parentController.collections.add( collection );
  41. const returned = collection.add( childController );
  42. expect( returned ).to.be.an.instanceof( Promise );
  43. expect( collection.get( 0 ) ).to.be.equal( childController );
  44. } );
  45. it( 'should add a child controller at given position', () => {
  46. const parentController = new Controller();
  47. const childController1 = new Controller();
  48. const childController2 = new Controller();
  49. const collection = new ControllerCollection( 'x' );
  50. parentController.collections.add( collection );
  51. collection.add( childController1 );
  52. collection.add( childController2, 0 );
  53. expect( collection.get( 0 ) ).to.be.equal( childController2 );
  54. expect( collection.get( 1 ) ).to.be.equal( childController1 );
  55. } );
  56. it( 'should initialize child controller if parent is ready', () => {
  57. const parentController = new Controller( null, new ParentView() );
  58. const childController = new Controller( null, new View() );
  59. const spy = testUtils.sinon.spy( childController, 'init' );
  60. const collection = new ControllerCollection( 'x' );
  61. parentController.collections.add( collection );
  62. collection.add( childController );
  63. collection.remove( childController );
  64. sinon.assert.notCalled( spy );
  65. return parentController.init()
  66. .then( () => {
  67. return collection.add( childController );
  68. } )
  69. .then( () => {
  70. sinon.assert.calledOnce( spy );
  71. } );
  72. } );
  73. it( 'should not initialize child controller twice', () => {
  74. const parentController = new Controller( null, new ParentView() );
  75. const childController = new Controller( null, new View() );
  76. const spy = testUtils.sinon.spy( childController, 'init' );
  77. const collection = new ControllerCollection( 'x' );
  78. parentController.collections.add( collection );
  79. return parentController.init()
  80. .then( () => {
  81. return childController.init();
  82. } )
  83. .then( () => {
  84. return collection.add( childController );
  85. } )
  86. .then( () => {
  87. sinon.assert.calledOnce( spy );
  88. } );
  89. } );
  90. } );
  91. describe( 'bind', () => {
  92. it( 'returns object', () => {
  93. expect( new ControllerCollection( 'foo' ).bind( {} ) ).to.be.an( 'object' );
  94. } );
  95. it( 'provides "as" interface', () => {
  96. const bind = new ControllerCollection( 'foo' ).bind( {} );
  97. expect( bind ).to.have.keys( 'as' );
  98. expect( bind.as ).to.be.a( 'function' );
  99. } );
  100. describe( 'as', () => {
  101. it( 'does not chain', () => {
  102. const controllers = new ControllerCollection( 'synced' );
  103. const returned = controllers.bind( models ).as( ItemController, ItemView );
  104. expect( returned ).to.be.undefined;
  105. } );
  106. describe( 'standard factory', () => {
  107. it( 'expands the initial collection of the models', () => {
  108. const controllers = new ControllerCollection( 'synced' );
  109. controllers.bind( models ).as( ItemController, ItemView );
  110. expect( controllers ).to.have.length( 5 );
  111. expect( controllers.get( 0 ).model.uid ).to.equal( '0' );
  112. expect( controllers.get( 4 ).model.uid ).to.equal( '4' );
  113. } );
  114. it( 'uses the controller and view classes to expand the collection', () => {
  115. const controllers = new ControllerCollection( 'synced' );
  116. controllers.bind( models ).as( ItemController, ItemView );
  117. expect( controllers.get( 0 ) ).to.be.instanceOf( ItemController );
  118. expect( controllers.get( 0 ).view ).to.be.instanceOf( ItemView );
  119. } );
  120. it( 'supports adding new models to the collection', () => {
  121. const controllers = new ControllerCollection( 'synced' );
  122. controllers.bind( models ).as( ItemController, ItemView );
  123. models.add( new Model( { uid: '6' } ) );
  124. models.add( new Model( { uid: '5' } ), 5 );
  125. expect( controllers.get( 5 ).model.uid ).to.equal( '5' );
  126. expect( controllers.get( 6 ).model.uid ).to.equal( '6' );
  127. expect( controllers ).to.have.length( 7 );
  128. } );
  129. it( 'supports removing models from the collection', () => {
  130. const controllers = new ControllerCollection( 'synced' );
  131. controllers.bind( models ).as( ItemController, ItemView );
  132. models.remove( 2 );
  133. models.remove( 3 );
  134. expect( controllers.map( c => c.id ) ).to.have.members( [ '0', '1', '3' ] );
  135. } );
  136. it( 'passes controller collection\'s locale to the views', () => {
  137. const locale = {};
  138. const controllers = new ControllerCollection( 'synced', locale );
  139. controllers.bind( models ).as( ItemController, ItemView );
  140. expect( controllers.get( 0 ).view.locale ).to.equal( locale );
  141. } );
  142. } );
  143. describe( 'custom factory', () => {
  144. it( 'expands the initial collection of the models', () => {
  145. const controllers = new ControllerCollection( 'synced' );
  146. controllers.bind( models ).as( ( model, locale ) => {
  147. return new ItemController( model, new ItemView( locale ) );
  148. } );
  149. expect( controllers ).to.have.length( 5 );
  150. expect( controllers.get( 0 ).model.uid ).to.equal( '0' );
  151. expect( controllers.get( 4 ).model.uid ).to.equal( '4' );
  152. } );
  153. it( 'uses the controller and view classes to expand the collection', () => {
  154. const controllers = new ControllerCollection( 'synced' );
  155. controllers.bind( models ).as( ( model, locale ) => {
  156. return new ItemController( model, new ItemView( locale ) );
  157. } );
  158. expect( controllers.get( 0 ) ).to.be.instanceOf( ItemController );
  159. expect( controllers.get( 0 ).view ).to.be.instanceOf( ItemView );
  160. } );
  161. it( 'supports adding new models to the collection', () => {
  162. const controllers = new ControllerCollection( 'synced' );
  163. controllers.bind( models ).as( ( model, locale ) => {
  164. return new ItemController( model, new ItemView( locale ) );
  165. } );
  166. models.add( new Model( { uid: '6' } ) );
  167. models.add( new Model( { uid: '5' } ), 5 );
  168. expect( controllers.get( 5 ).model.uid ).to.equal( '5' );
  169. expect( controllers.get( 6 ).model.uid ).to.equal( '6' );
  170. expect( controllers ).to.have.length( 7 );
  171. } );
  172. it( 'supports removing models from the collection', () => {
  173. const controllers = new ControllerCollection( 'synced' );
  174. controllers.bind( models ).as( ( model, locale ) => {
  175. return new ItemController( model, new ItemView( locale ) );
  176. } );
  177. models.remove( 2 );
  178. models.remove( 3 );
  179. expect( controllers.map( c => c.id ) ).to.have.members( [ '0', '1', '3' ] );
  180. } );
  181. it( 'passes controller collection\'s locale to the views', () => {
  182. const locale = {};
  183. const controllers = new ControllerCollection( 'synced', locale );
  184. controllers.bind( models ).as( ( model, locale ) => {
  185. return new ItemController( model, new ItemView( locale ) );
  186. } );
  187. expect( controllers.get( 0 ).view.locale ).to.equal( locale );
  188. } );
  189. } );
  190. } );
  191. } );
  192. } );
  193. function defineParentViewClass() {
  194. ParentView = class extends View {
  195. constructor() {
  196. super();
  197. this.element = document.createElement( 'span' );
  198. this.register( 'x', true );
  199. }
  200. };
  201. }
  202. function defineItemControllerClass() {
  203. ItemController = class extends Controller {
  204. constructor( model, view ) {
  205. super( model, view );
  206. view.model.bind( 'uid' ).to( model );
  207. }
  208. };
  209. }
  210. function defineItemViewClass() {
  211. ItemView = class extends View {
  212. constructor( locale ) {
  213. super( locale );
  214. const bind = this.bind;
  215. this.template = new Template( {
  216. tag: 'li',
  217. attributes: {
  218. id: bind.to( 'uid' )
  219. }
  220. } );
  221. }
  222. };
  223. }
  224. function createModelCollection() {
  225. models = new Collection( { idProperty: 'uid' } );
  226. for ( let i = 0; i < 5; i++ ) {
  227. models.add( new Model( {
  228. uid: Number( i ).toString()
  229. } ) );
  230. }
  231. }