view.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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. setTestViewClass( function() {
  45. return {
  46. tag: 'p',
  47. attributes: {
  48. 'class': this.bind( 'foo' )
  49. },
  50. text: 'abc'
  51. };
  52. } );
  53. view = new TestView( { foo: 'bar' } );
  54. expect( view.el.outerHTML ).to.be.equal( '<p class="bar">abc</p>' );
  55. view.model.foo = 'baz';
  56. expect( view.el.outerHTML ).to.be.equal( '<p class="baz">abc</p>' );
  57. } );
  58. it( 'allows binding "text" to the model', function() {
  59. setTestViewClass( function() {
  60. return {
  61. tag: 'p',
  62. children: [
  63. {
  64. tag: 'b',
  65. text: 'baz'
  66. }
  67. ],
  68. text: this.bind( 'foo' )
  69. };
  70. } );
  71. view = new TestView( { foo: 'bar' } );
  72. expect( view.el.outerHTML ).to.be.equal( '<p>bar<b>baz</b></p>' );
  73. // TODO: A solution to avoid nuking the children?
  74. view.model.foo = 'qux';
  75. expect( view.el.outerHTML ).to.be.equal( '<p>qux</p>' );
  76. } );
  77. it( 'allows binding to the model with value processing', function() {
  78. var callback = ( el, value ) =>
  79. ( value > 0 ? 'positive' : 'negative' );
  80. setTestViewClass( function() {
  81. return {
  82. tag: 'p',
  83. attributes: {
  84. 'class': this.bind( 'foo', callback )
  85. },
  86. text: this.bind( 'foo', callback )
  87. };
  88. } );
  89. view = new TestView( { foo: 3 } );
  90. expect( view.el.outerHTML ).to.be.equal( '<p class="positive">positive</p>' );
  91. view.model.foo = -7;
  92. expect( view.el.outerHTML ).to.be.equal( '<p class="negative">negative</p>' );
  93. } );
  94. it( 'allows binding to the model with custom callback', function() {
  95. setTestViewClass( function() {
  96. return {
  97. tag: 'p',
  98. attributes: {
  99. 'class': this.bind( 'foo', ( el, value ) => {
  100. el.innerHTML = value;
  101. if ( value == 'changed' ) {
  102. return value;
  103. }
  104. } )
  105. },
  106. text: 'bar'
  107. };
  108. } );
  109. view = new TestView( { foo: 'moo' } );
  110. expect( view.el.outerHTML ).to.be.equal( '<p>moo</p>' );
  111. view.model.foo = 'changed';
  112. expect( view.el.outerHTML ).to.be.equal( '<p class="changed">changed</p>' );
  113. } );
  114. } );
  115. describe( 'listeners', function() {
  116. it( 'accept plain definitions', function() {
  117. setTestViewClass( function() {
  118. return {
  119. tag: 'p',
  120. listeners: {
  121. x: 'a',
  122. y: [ 'b', 'c' ],
  123. }
  124. };
  125. } );
  126. view = new TestView();
  127. view.el.dispatchEvent( new Event( 'x' ) );
  128. view.el.dispatchEvent( new Event( 'y' ) );
  129. } );
  130. it( 'accept definition with selectors', function() {
  131. setTestViewClass( function() {
  132. return {
  133. tag: 'p',
  134. children: [
  135. {
  136. tag: 'span',
  137. 'class': '.y'
  138. },
  139. {
  140. tag: 'div',
  141. children: [
  142. {
  143. tag: 'span',
  144. 'class': '.y'
  145. }
  146. ],
  147. }
  148. ],
  149. listeners: {
  150. 'x@.y': 'a',
  151. 'y@div': 'b'
  152. }
  153. };
  154. } );
  155. view = new TestView();
  156. view.el.childNodes[ 0 ].dispatchEvent( new Event( 'x' ) );
  157. view.el.childNodes[ 1 ].dispatchEvent( new Event( 'x' ) ); // false
  158. view.el.childNodes[ 1 ].childNodes[ 0 ].dispatchEvent( new Event( 'x' ) );
  159. view.el.childNodes[ 0 ].dispatchEvent( new Event( 'y' ) ); // false
  160. view.el.childNodes[ 1 ].dispatchEvent( new Event( 'y' ) );
  161. view.el.childNodes[ 1 ].childNodes[ 0 ].dispatchEvent( new Event( 'y' ) ); // false
  162. } );
  163. } );
  164. describe( 'render', function() {
  165. it( 'creates an element from template', function() {
  166. view = new TestView( { a: 1 } );
  167. expect( view.el ).to.be.an.instanceof( HTMLElement );
  168. expect( view.el.nodeName ).to.be.equal( 'A' );
  169. } );
  170. } );
  171. describe( 'destroy', function() {
  172. it( 'detaches the model', function() {
  173. expect( view.model ).to.be.an.instanceof( modules.model );
  174. view.destroy();
  175. expect( view.model ).to.be.null;
  176. } );
  177. it( 'detaches the element', function() {
  178. view = new TestView();
  179. // Append the views's element to some container.
  180. var container = document.createElement( 'div' );
  181. container.appendChild( view.el );
  182. expect( view.el.nodeName ).to.be.equal( 'A' );
  183. expect( view.el ).to.be.an.instanceof( HTMLElement );
  184. expect( view.el.parentNode ).to.be.equal( container );
  185. view.destroy();
  186. expect( view.el ).to.be.an.instanceof( HTMLElement );
  187. expect( view.el.parentNode ).to.be.null;
  188. } );
  189. it( 'destroys child regions', function() {
  190. var Region = modules[ 'ui/region' ];
  191. var region = new Region();
  192. var spy = bender.sinon.spy( region, 'destroy' );
  193. view.regions.add( region );
  194. view.destroy();
  195. expect( view.regions ).to.have.length( 0 );
  196. expect( spy.calledOnce ).to.be.true;
  197. } );
  198. it( 'detaches bound model listeners', function() {
  199. setTestViewClass( function() {
  200. return {
  201. tag: 'p',
  202. text: this.bind( 'foo' )
  203. };
  204. } );
  205. view = new TestView( { foo: 'bar' } );
  206. var model = view.model;
  207. expect( view.el.outerHTML ).to.be.equal( '<p>bar</p>' );
  208. model.foo = 'baz';
  209. expect( view.el.outerHTML ).to.be.equal( '<p>baz</p>' );
  210. view.destroy();
  211. model.foo = 'abc';
  212. expect( view.el.outerHTML ).to.be.equal( '<p>baz</p>' );
  213. } );
  214. } );
  215. function createViewInstance() {
  216. View = modules[ 'ui/view' ];
  217. view = new View( { a: 'foo', b: 42 } );
  218. setTestViewClass( () => {
  219. return { tag: 'a' };
  220. } );
  221. }
  222. function setTestViewClass( template ) {
  223. TestView = class V extends View {
  224. constructor( model ) {
  225. super( model );
  226. this.template = template.call( this );
  227. }
  228. };
  229. }