8
0

controllercollection.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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 modelCollection;
  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( 'activates model collection synchronization', () => {
  30. const modelCollection = new Collection( {
  31. idProperty: 'uid'
  32. } );
  33. modelCollection.add( new Model( {
  34. uid: 'foo'
  35. } ) );
  36. const controllers = new ControllerCollection( 'synced', modelCollection, ItemController, ItemView );
  37. expect( controllers ).to.have.length( 1 );
  38. } );
  39. } );
  40. describe( 'add', () => {
  41. it( 'should add a child controller and return promise', () => {
  42. const parentController = new Controller();
  43. const childController = new Controller();
  44. const collection = new ControllerCollection( 'x' );
  45. parentController.collections.add( collection );
  46. const returned = collection.add( childController );
  47. expect( returned ).to.be.an.instanceof( Promise );
  48. expect( collection.get( 0 ) ).to.be.equal( childController );
  49. } );
  50. it( 'should add a child controller at given position', () => {
  51. const parentController = new Controller();
  52. const childController1 = new Controller();
  53. const childController2 = new Controller();
  54. const collection = new ControllerCollection( 'x' );
  55. parentController.collections.add( collection );
  56. collection.add( childController1 );
  57. collection.add( childController2, 0 );
  58. expect( collection.get( 0 ) ).to.be.equal( childController2 );
  59. expect( collection.get( 1 ) ).to.be.equal( childController1 );
  60. } );
  61. it( 'should initialize child controller if parent is ready', () => {
  62. const parentController = new Controller( null, new ParentView() );
  63. const childController = new Controller( null, new View() );
  64. const spy = testUtils.sinon.spy( childController, 'init' );
  65. const collection = new ControllerCollection( 'x' );
  66. parentController.collections.add( collection );
  67. collection.add( childController );
  68. collection.remove( childController );
  69. sinon.assert.notCalled( spy );
  70. return parentController.init()
  71. .then( () => {
  72. return collection.add( childController );
  73. } )
  74. .then( () => {
  75. sinon.assert.calledOnce( spy );
  76. } );
  77. } );
  78. it( 'should not initialize child controller twice', () => {
  79. const parentController = new Controller( null, new ParentView() );
  80. const childController = new Controller( null, new View() );
  81. const spy = testUtils.sinon.spy( childController, 'init' );
  82. const collection = new ControllerCollection( 'x' );
  83. parentController.collections.add( collection );
  84. return parentController.init()
  85. .then( () => {
  86. return childController.init();
  87. } )
  88. .then( () => {
  89. return collection.add( childController );
  90. } )
  91. .then( () => {
  92. sinon.assert.calledOnce( spy );
  93. } );
  94. } );
  95. } );
  96. describe( '_sync', () => {
  97. it( 'expands the initial collection of the models', () => {
  98. const controllers = new ControllerCollection( 'synced' );
  99. controllers._sync( modelCollection, ItemController, ItemView );
  100. expect( controllers ).to.have.length( 5 );
  101. expect( controllers.get( 0 ).model.uid ).to.equal( '0' );
  102. expect( controllers.get( 4 ).model.uid ).to.equal( '4' );
  103. } );
  104. it( 'uses the controller and view classes to expand the collection', () => {
  105. const controllers = new ControllerCollection( 'synced' );
  106. controllers._sync( modelCollection, ItemController, ItemView );
  107. expect( controllers.get( 0 ) ).to.be.instanceOf( ItemController );
  108. expect( controllers.get( 0 ).view ).to.be.instanceOf( ItemView );
  109. } );
  110. it( 'supports adding new models to the collection', () => {
  111. const controllers = new ControllerCollection( 'synced' );
  112. controllers._sync( modelCollection, ItemController, ItemView );
  113. modelCollection.add( new Model( { uid: '6' } ) );
  114. modelCollection.add( new Model( { uid: '5' } ), 5 );
  115. expect( controllers.get( 5 ).model.uid ).to.equal( '5' );
  116. expect( controllers.get( 6 ).model.uid ).to.equal( '6' );
  117. expect( controllers ).to.have.length( 7 );
  118. } );
  119. it( 'supports removing models from the collection', () => {
  120. const controllers = new ControllerCollection( 'synced' );
  121. controllers._sync( modelCollection, ItemController, ItemView );
  122. modelCollection.remove( 2 );
  123. modelCollection.remove( 3 );
  124. expect( controllers.map( c => c.id ) ).to.have.members( [ '0', '1', '3' ] );
  125. } );
  126. } );
  127. } );
  128. function defineParentViewClass() {
  129. ParentView = class extends View {
  130. constructor() {
  131. super();
  132. this.element = document.createElement( 'span' );
  133. this.register( 'x', true );
  134. }
  135. };
  136. }
  137. function defineItemControllerClass() {
  138. ItemController = class extends Controller {
  139. constructor( model, view ) {
  140. super( model, view );
  141. view.model.bind( 'uid' ).to( model );
  142. }
  143. };
  144. }
  145. function defineItemViewClass() {
  146. ItemView = class extends View {
  147. constructor() {
  148. super();
  149. const bind = this.bind;
  150. this.template = new Template( {
  151. tag: 'li',
  152. attributes: {
  153. id: bind.to( 'uid' )
  154. }
  155. } );
  156. }
  157. };
  158. }
  159. function createModelCollection() {
  160. modelCollection = new Collection( { idProperty: 'uid' } );
  161. for ( let i = 0; i < 5; i++ ) {
  162. modelCollection.add( new Model( {
  163. uid: Number( i ).toString()
  164. } ) );
  165. }
  166. }