view.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* global document, HTMLElement */
  6. /* bender-tags: core, ui */
  7. 'use strict';
  8. var modules = bender.amd.require( 'ckeditor', 'ui/view', 'ui/region', 'ckeditorerror', 'model' );
  9. var View, TestView;
  10. var view;
  11. bender.tools.createSinonSandbox();
  12. beforeEach( createViewInstance );
  13. describe( 'constructor', function() {
  14. it( 'accepts the model', function() {
  15. expect( view.model ).to.be.an.instanceof( modules.model );
  16. expect( view ).to.have.deep.property( 'model.a', 'foo' );
  17. expect( view ).to.have.deep.property( 'model.b', 42 );
  18. } );
  19. } );
  20. describe( 'instance', function() {
  21. it( 'has no default element', function() {
  22. expect( () => view.el ).to.throw( modules.ckeditorerror );
  23. } );
  24. it( 'has no default template', function() {
  25. expect( view.template ).to.be.undefined();
  26. } );
  27. it( 'has no default regions', function() {
  28. expect( view.regions ).to.have.length( 0 );
  29. } );
  30. } );
  31. describe( 'bind', function() {
  32. it( 'returns a function that passes arguments', function() {
  33. var spy = bender.sinon.spy();
  34. var callback = view.bind( 'a', spy );
  35. expect( spy.called ).to.be.false;
  36. callback( 'el', 'updater' );
  37. sinon.assert.calledOnce( spy );
  38. sinon.assert.calledWithExactly( spy, 'el', 'foo' );
  39. view.model.a = 'bar';
  40. sinon.assert.calledTwice( spy );
  41. expect( spy.secondCall.calledWithExactly( 'el', 'bar' ) ).to.be.true;
  42. } );
  43. it( 'allows binding attribute to the model', function() {
  44. class TestView extends View {
  45. constructor( model ) {
  46. super( model );
  47. this.template = {
  48. tag: 'p',
  49. attributes: {
  50. 'class': this.bind( 'foo' )
  51. },
  52. text: 'abc'
  53. };
  54. }
  55. }
  56. view = new TestView( { foo: 'bar' } );
  57. expect( view.el.outerHTML ).to.be.equal( '<p class="bar">abc</p>' );
  58. view.model.foo = 'baz';
  59. expect( view.el.outerHTML ).to.be.equal( '<p class="baz">abc</p>' );
  60. } );
  61. it( 'allows binding "text" to the model', function() {
  62. class TestView extends View {
  63. constructor( model ) {
  64. super( model );
  65. this.template = {
  66. tag: 'p',
  67. children: [
  68. {
  69. tag: 'b',
  70. text: 'baz'
  71. }
  72. ],
  73. text: this.bind( 'foo' )
  74. };
  75. }
  76. }
  77. view = new TestView( { foo: 'bar' } );
  78. expect( view.el.outerHTML ).to.be.equal( '<p>bar<b>baz</b></p>' );
  79. // TODO: A solution to avoid nuking the children?
  80. view.model.foo = 'qux';
  81. expect( view.el.outerHTML ).to.be.equal( '<p>qux</p>' );
  82. } );
  83. it( 'allows binding to the model with value processing', function() {
  84. class TestView extends View {
  85. constructor( model ) {
  86. super( model );
  87. var callback = ( el, value ) =>
  88. ( value > 0 ? 'positive' : 'negative' );
  89. this.template = {
  90. tag: 'p',
  91. attributes: {
  92. 'class': this.bind( 'foo', callback )
  93. },
  94. text: this.bind( 'foo', callback )
  95. };
  96. }
  97. }
  98. view = new TestView( { foo: 3 } );
  99. expect( view.el.outerHTML ).to.be.equal( '<p class="positive">positive</p>' );
  100. view.model.foo = -7;
  101. expect( view.el.outerHTML ).to.be.equal( '<p class="negative">negative</p>' );
  102. } );
  103. it( 'allows binding to the model with custom callback', function() {
  104. class TestView extends View {
  105. constructor( model ) {
  106. super( model );
  107. this.template = {
  108. tag: 'p',
  109. attributes: {
  110. 'class': this.bind( 'foo', ( el, value ) => {
  111. el.innerHTML = value;
  112. if ( value == 'changed' ) {
  113. return value;
  114. }
  115. } )
  116. },
  117. text: 'bar'
  118. };
  119. }
  120. }
  121. view = new TestView( { foo: 'moo' } );
  122. expect( view.el.outerHTML ).to.be.equal( '<p>moo</p>' );
  123. view.model.foo = 'changed';
  124. expect( view.el.outerHTML ).to.be.equal( '<p class="changed">changed</p>' );
  125. } );
  126. } );
  127. describe( 'render', function() {
  128. it( 'creates an element from template', function() {
  129. view = new TestView( { a: 1 } );
  130. expect( view.el ).to.be.an.instanceof( HTMLElement );
  131. expect( view.el.nodeName ).to.be.equal( 'A' );
  132. } );
  133. } );
  134. describe( 'destroy', function() {
  135. it( 'detaches the model', function() {
  136. expect( view.model ).to.be.an.instanceof( modules.model );
  137. view.destroy();
  138. expect( view.model ).to.be.null;
  139. } );
  140. it( 'detaches the element', function() {
  141. view = new TestView();
  142. // Append the views's element to some container.
  143. var container = document.createElement( 'div' );
  144. container.appendChild( view.el );
  145. expect( view.el.nodeName ).to.be.equal( 'A' );
  146. expect( view.el ).to.be.an.instanceof( HTMLElement );
  147. expect( view.el.parentNode ).to.be.equal( container );
  148. view.destroy();
  149. expect( view.el ).to.be.an.instanceof( HTMLElement );
  150. expect( view.el.parentNode ).to.be.null;
  151. } );
  152. it( 'destroys child regions', function() {
  153. var Region = modules[ 'ui/region' ];
  154. var region = new Region();
  155. var spy = bender.sinon.spy( region, 'destroy' );
  156. view.regions.add( region );
  157. view.destroy();
  158. expect( view.regions ).to.have.length( 0 );
  159. expect( spy.calledOnce ).to.be.true;
  160. } );
  161. it( 'detaches bound model listeners', function() {
  162. class TestView extends View {
  163. constructor( model ) {
  164. super( model );
  165. this.template = {
  166. tag: 'p',
  167. text: this.bind( 'foo' )
  168. };
  169. }
  170. }
  171. view = new TestView( { foo: 'bar' } );
  172. var model = view.model;
  173. expect( view.el.outerHTML ).to.be.equal( '<p>bar</p>' );
  174. model.foo = 'baz';
  175. expect( view.el.outerHTML ).to.be.equal( '<p>baz</p>' );
  176. view.destroy();
  177. model.foo = 'abc';
  178. expect( view.el.outerHTML ).to.be.equal( '<p>baz</p>' );
  179. } );
  180. } );
  181. function createViewInstance() {
  182. View = modules[ 'ui/view' ];
  183. view = new View( { a: 'foo', b: 42 } );
  184. class T extends View {
  185. constructor() {
  186. super();
  187. this.template = { tag: 'a' };
  188. }
  189. }
  190. TestView = T;
  191. }