floatingtoolbarview.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* global Event */
  6. import global from '@ckeditor/ckeditor5-utils/src/dom/global';
  7. import FloatingToolbarView from '../../../src/toolbar/floating/floatingtoolbarview';
  8. import ToolbarView from '../../../src/toolbar/toolbarview';
  9. import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
  10. import * as positionUtils from '@ckeditor/ckeditor5-utils/src/dom/position';
  11. testUtils.createSinonSandbox();
  12. describe( 'FloatingToolbarView', () => {
  13. let locale, view, target;
  14. beforeEach( () => {
  15. locale = {};
  16. view = new FloatingToolbarView( locale );
  17. target = global.document.createElement( 'a' );
  18. global.document.body.appendChild( view.element );
  19. global.document.body.appendChild( target );
  20. view.targetElement = target;
  21. return view.init();
  22. } );
  23. afterEach( () => {
  24. view.element.remove();
  25. return view.destroy();
  26. } );
  27. describe( 'constructor()', () => {
  28. it( 'should inherit from ToolbarView', () => {
  29. expect( view ).to.be.instanceOf( ToolbarView );
  30. } );
  31. it( 'should set view#locale', () => {
  32. expect( view.locale ).to.equal( locale );
  33. } );
  34. it( 'should set #isActive', () => {
  35. expect( view.isActive ).to.be.false;
  36. } );
  37. it( 'should set #top', () => {
  38. expect( view.top ).to.equal( 0 );
  39. } );
  40. it( 'should set #left', () => {
  41. expect( view.left ).to.equal( 0 );
  42. } );
  43. it( 'should set #targetElement', () => {
  44. view = new FloatingToolbarView( locale );
  45. expect( view.targetElement ).to.be.null;
  46. } );
  47. } );
  48. describe( 'template', () => {
  49. it( 'should create element from template', () => {
  50. expect( view.element.classList.contains( 'ck-toolbar_floating' ) ).to.be.true;
  51. } );
  52. describe( 'bindings', () => {
  53. describe( 'class', () => {
  54. it( 'reacts on #isActive', () => {
  55. view.isActive = false;
  56. expect( view.element.classList.contains( 'ck-toolbar_active' ) ).to.be.false;
  57. view.isActive = true;
  58. expect( view.element.classList.contains( 'ck-toolbar_active' ) ).to.be.true;
  59. } );
  60. } );
  61. describe( 'style', () => {
  62. it( 'reacts on #top', () => {
  63. view.top = 30;
  64. expect( view.element.style.top ).to.equal( '30px' );
  65. } );
  66. it( 'reacts on #left', () => {
  67. view.left = 20;
  68. expect( view.element.style.left ).to.equal( '20px' );
  69. } );
  70. } );
  71. } );
  72. } );
  73. describe( 'init()', () => {
  74. it( 'calls #_updatePosition on window.scroll', () => {
  75. const spy = sinon.spy( view, '_updatePosition' );
  76. global.window.dispatchEvent( new Event( 'scroll', { bubbles: true } ) );
  77. sinon.assert.calledOnce( spy );
  78. } );
  79. it( 'calls #_updatePosition on #change:isActive', () => {
  80. view.isActive = false;
  81. const spy = sinon.spy( view, '_updatePosition' );
  82. view.isActive = true;
  83. sinon.assert.calledOnce( spy );
  84. view.isActive = false;
  85. sinon.assert.calledTwice( spy );
  86. } );
  87. } );
  88. describe( '_updatePosition()', () => {
  89. it( 'does not update when not #isActive', () => {
  90. const spy = testUtils.sinon.spy( positionUtils, 'getOptimalPosition' );
  91. view.isActive = false;
  92. view._updatePosition();
  93. sinon.assert.notCalled( spy );
  94. view.isActive = true;
  95. view._updatePosition();
  96. // Note: #_updatePosition() is called on #change:isActive.
  97. sinon.assert.calledTwice( spy );
  98. } );
  99. it( 'uses getOptimalPosition() utility', () => {
  100. const { nw, sw, ne, se } = FloatingToolbarView.defaultPositions;
  101. view.isActive = true;
  102. const spy = testUtils.sinon.stub( positionUtils, 'getOptimalPosition' ).returns( {
  103. top: 5,
  104. left: 10
  105. } );
  106. view._updatePosition();
  107. sinon.assert.calledWithMatch( spy, sinon.match( {
  108. element: view.element,
  109. target: target,
  110. positions: [ nw, sw, ne, se ],
  111. limiter: global.document.body,
  112. fitInViewport: true
  113. } ) );
  114. expect( view.top ).to.equal( 5 );
  115. expect( view.left ).to.equal( 10 );
  116. } );
  117. } );
  118. describe( 'defaultPositions', () => {
  119. let targetRect, toolbarRect, defaultPositions;
  120. beforeEach( () => {
  121. defaultPositions = FloatingToolbarView.defaultPositions;
  122. targetRect = {
  123. top: 10,
  124. width: 100,
  125. left: 10,
  126. height: 10,
  127. bottom: 20
  128. };
  129. toolbarRect = {
  130. width: 20,
  131. height: 10
  132. };
  133. } );
  134. it( 'should provide "nw" position', () => {
  135. expect( defaultPositions.nw( targetRect, toolbarRect ) ).to.deep.equal( {
  136. top: 0,
  137. left: 10,
  138. name: 'nw'
  139. } );
  140. } );
  141. it( 'should provide "sw" position', () => {
  142. expect( defaultPositions.sw( targetRect, toolbarRect ) ).to.deep.equal( {
  143. top: 20,
  144. left: 10,
  145. name: 'sw'
  146. } );
  147. } );
  148. it( 'should provide "ne" position', () => {
  149. expect( defaultPositions.ne( targetRect, toolbarRect ) ).to.deep.equal( {
  150. top: 0,
  151. left: 90,
  152. name: 'ne'
  153. } );
  154. } );
  155. it( 'should provide "se" position', () => {
  156. expect( defaultPositions.se( targetRect, toolbarRect ) ).to.deep.equal( {
  157. top: 20,
  158. left: 90,
  159. name: 'se'
  160. } );
  161. } );
  162. } );
  163. } );