inlineeditoruiview.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. import InlineEditorUIView from '../src/inlineeditoruiview';
  6. import EditingView from '@ckeditor/ckeditor5-engine/src/view/view';
  7. import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview';
  8. import BalloonPanelView from '@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview';
  9. import InlineEditableUIView from '@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview';
  10. import Locale from '@ckeditor/ckeditor5-utils/src/locale';
  11. import createRoot from '@ckeditor/ckeditor5-engine/tests/view/_utils/createroot.js';
  12. import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect';
  13. import toUnit from '@ckeditor/ckeditor5-utils/src/dom/tounit';
  14. import global from '@ckeditor/ckeditor5-utils/src/dom/global';
  15. import ResizeObserver from '@ckeditor/ckeditor5-utils/src/dom/resizeobserver';
  16. const toPx = toUnit( 'px' );
  17. import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
  18. describe( 'InlineEditorUIView', () => {
  19. let locale, view, editingView, editingViewRoot;
  20. let resizeCallback;
  21. testUtils.createSinonSandbox();
  22. beforeEach( () => {
  23. locale = new Locale();
  24. editingView = new EditingView();
  25. editingViewRoot = createRoot( editingView.document );
  26. view = new InlineEditorUIView( locale, editingView );
  27. view.editable.name = editingViewRoot.rootName;
  28. // Make sure other tests of the editor do not affect tests that follow.
  29. // Without it, if an instance of ResizeObserver already exists somewhere undestroyed
  30. // in DOM, the following DOM mock will have no effect.
  31. ResizeObserver._observerInstance = null;
  32. testUtils.sinon.stub( global.window, 'ResizeObserver' ).callsFake( callback => {
  33. resizeCallback = callback;
  34. return {
  35. observe: sinon.spy(),
  36. unobserve: sinon.spy()
  37. };
  38. } );
  39. } );
  40. afterEach( () => {
  41. view.destroy();
  42. } );
  43. describe( 'constructor()', () => {
  44. describe( '#toolbar', () => {
  45. it( 'is created', () => {
  46. expect( view.toolbar ).to.be.instanceof( ToolbarView );
  47. } );
  48. it( 'is given a locale object', () => {
  49. expect( view.toolbar.locale ).to.equal( locale );
  50. } );
  51. it( 'is not rendered', () => {
  52. expect( view.toolbar.isRendered ).to.be.false;
  53. } );
  54. it( 'should have the shouldGroupWhenFull option set based on constructor options', () => {
  55. const view = new InlineEditorUIView( locale, editingView, null, {
  56. shouldToolbarGroupWhenFull: true
  57. } );
  58. expect( view.toolbar.options.shouldGroupWhenFull ).to.be.true;
  59. view.destroy();
  60. } );
  61. } );
  62. describe( '#panel', () => {
  63. it( 'is created', () => {
  64. expect( view.panel ).to.be.instanceof( BalloonPanelView );
  65. } );
  66. it( 'is given a locale object', () => {
  67. expect( view.panel.locale ).to.equal( locale );
  68. } );
  69. it( 'gets view.panel#withArrow set', () => {
  70. expect( view.panel.withArrow ).to.be.false;
  71. } );
  72. it( 'is not rendered', () => {
  73. expect( view.panel.isRendered ).to.be.false;
  74. } );
  75. } );
  76. describe( '#editable', () => {
  77. it( 'is created', () => {
  78. expect( view.editable ).to.be.instanceof( InlineEditableUIView );
  79. } );
  80. it( 'is given a locale object', () => {
  81. expect( view.editable.locale ).to.equal( locale );
  82. } );
  83. it( 'is not rendered', () => {
  84. expect( view.editable.isRendered ).to.be.false;
  85. } );
  86. } );
  87. } );
  88. describe( 'render()', () => {
  89. beforeEach( () => {
  90. view.render();
  91. } );
  92. describe( '#toolbar', () => {
  93. it( 'is given the right CSS classes', () => {
  94. expect( view.toolbar.element.classList.contains( 'ck-toolbar_floating' ) ).to.be.true;
  95. } );
  96. it( 'sets the default value of the #viewportTopOffset attribute', () => {
  97. expect( view.viewportTopOffset ).to.equal( 0 );
  98. } );
  99. describe( 'automatic resizing when shouldToolbarGroupWhenFull is "true"', () => {
  100. it( 'should set and update toolbar max-width according to the width of the editable element', () => {
  101. const locale = new Locale();
  102. const editingView = new EditingView();
  103. const editingViewRoot = createRoot( editingView.document );
  104. const view = new InlineEditorUIView( locale, editingView, null, {
  105. shouldToolbarGroupWhenFull: true
  106. } );
  107. view.editable.name = editingViewRoot.rootName;
  108. view.render();
  109. const editableElement = view.editable.element;
  110. // View element should be inside the body, otherwise the `Rect` instance will complain
  111. // that it's not available in the DOM.
  112. global.document.body.appendChild( editableElement );
  113. editableElement.style.width = '400px';
  114. resizeCallback( [ {
  115. target: editableElement,
  116. contentRect: new Rect( editableElement )
  117. } ] );
  118. // Include paddings.
  119. expect( view.toolbar.maxWidth ).to.equal( toPx( new Rect( editableElement ).width ) );
  120. editableElement.style.width = '200px';
  121. resizeCallback( [ {
  122. target: editableElement,
  123. contentRect: new Rect( editableElement )
  124. } ] );
  125. // Include paddings.
  126. expect( view.toolbar.maxWidth ).to.equal( toPx( new Rect( editableElement ).width ) );
  127. editableElement.remove();
  128. view.destroy();
  129. } );
  130. } );
  131. } );
  132. describe( '#panel', () => {
  133. it( 'is given the right CSS class', () => {
  134. expect( view.panel.element.classList.contains( 'ck-toolbar-container' ) ).to.be.true;
  135. } );
  136. it( 'is put into the #body collection', () => {
  137. expect( view.body.get( 0 ) ).to.equal( view.panel );
  138. } );
  139. } );
  140. describe( '#editable', () => {
  141. it( 'is registered as a child', () => {
  142. const spy = sinon.spy( view.editable, 'destroy' );
  143. view.destroy();
  144. sinon.assert.calledOnce( spy );
  145. } );
  146. } );
  147. } );
  148. describe( 'init()', () => {
  149. it( 'appends #toolbar to panel#content', () => {
  150. locale = new Locale();
  151. const view = new InlineEditorUIView( locale, editingView );
  152. view.editable.name = editingViewRoot.rootName;
  153. expect( view.panel.content ).to.have.length( 0 );
  154. view.render();
  155. expect( view.panel.content.get( 0 ) ).to.equal( view.toolbar );
  156. view.destroy();
  157. } );
  158. } );
  159. describe( 'panelPositions', () => {
  160. it( 'returns the positions in the right order (uiLanguageDirection="ltr")', () => {
  161. locale.uiLanguageDirection = 'ltr';
  162. const uiView = new InlineEditorUIView( locale, editingView );
  163. const positions = uiView.panelPositions;
  164. const editableRect = {
  165. top: 100,
  166. bottom: 200,
  167. left: 100,
  168. right: 100,
  169. width: 100,
  170. height: 100
  171. };
  172. const panelRect = {
  173. width: 50,
  174. height: 50
  175. };
  176. expect( positions ).to.have.length( 2 );
  177. expect( positions[ 0 ]( editableRect, panelRect ).name ).to.equal( 'toolbar_west' );
  178. expect( positions[ 1 ]( editableRect, panelRect ).name ).to.equal( 'toolbar_east' );
  179. } );
  180. it( 'returns the positions in the right order (uiLanguageDirection="rtl")', () => {
  181. locale.uiLanguageDirection = 'rtl';
  182. const uiView = new InlineEditorUIView( locale, editingView );
  183. const positions = uiView.panelPositions;
  184. const editableRect = {
  185. top: 100,
  186. bottom: 200,
  187. left: 100,
  188. right: 100,
  189. width: 100,
  190. height: 100
  191. };
  192. const panelRect = {
  193. width: 50,
  194. height: 50
  195. };
  196. expect( positions ).to.have.length( 2 );
  197. expect( positions[ 0 ]( editableRect, panelRect ).name ).to.equal( 'toolbar_east' );
  198. expect( positions[ 1 ]( editableRect, panelRect ).name ).to.equal( 'toolbar_west' );
  199. } );
  200. describe( 'west', () => {
  201. testTopPositions( 0, 100 );
  202. } );
  203. describe( 'east', () => {
  204. testTopPositions( 1, 150 );
  205. } );
  206. function testTopPositions( positionIndex, expectedLeft ) {
  207. it( 'positions the panel above editable when there\'s enough space', () => {
  208. const position = view.panelPositions[ positionIndex ];
  209. const editableRect = {
  210. top: 101, // !
  211. bottom: 200,
  212. left: 100,
  213. right: 100,
  214. width: 100,
  215. height: 100
  216. };
  217. const panelRect = {
  218. width: 50,
  219. height: 100 // !
  220. };
  221. const { top, left } = position( editableRect, panelRect );
  222. expect( top ).to.equal( 1 );
  223. expect( left ).to.equal( expectedLeft );
  224. } );
  225. it( 'positions the panel over the editable when there\'s not enough space above (1)', () => {
  226. const position = view.panelPositions[ positionIndex ];
  227. const editableRect = {
  228. top: 100, // !
  229. bottom: 300,
  230. left: 100,
  231. right: 100,
  232. width: 100,
  233. height: 200
  234. };
  235. const panelRect = {
  236. width: 50,
  237. height: 100 // !
  238. };
  239. const { top, left } = position( editableRect, panelRect );
  240. expect( top ).to.equal( 0 );
  241. expect( left ).to.equal( expectedLeft );
  242. } );
  243. it( 'positions the panel over the editable when there\'s not enough space above (2)', () => {
  244. const position = view.panelPositions[ positionIndex ];
  245. const editableRect = {
  246. top: 99, // !
  247. bottom: 399,
  248. left: 100,
  249. right: 100,
  250. width: 100,
  251. height: 200
  252. };
  253. const panelRect = {
  254. width: 50,
  255. height: 100 // !
  256. };
  257. const { top, left } = position( editableRect, panelRect );
  258. expect( top ).to.equal( 0 );
  259. expect( left ).to.equal( expectedLeft );
  260. } );
  261. it( 'positions the panel over the editable when there\'s not enough space above (3)', () => {
  262. const position = view.panelPositions[ positionIndex ];
  263. const editableRect = {
  264. top: 51, // !
  265. bottom: 399,
  266. left: 100,
  267. right: 100,
  268. width: 100,
  269. height: 200
  270. };
  271. const panelRect = {
  272. width: 50,
  273. height: 100 // !
  274. };
  275. const { top, left } = position( editableRect, panelRect );
  276. expect( top ).to.equal( 0 );
  277. expect( left ).to.equal( expectedLeft );
  278. } );
  279. it( 'positions the panel below the editable when there\'s not enough space above/over', () => {
  280. const position = view.panelPositions[ positionIndex ];
  281. const editableRect = {
  282. top: 50,
  283. bottom: 150, // !
  284. left: 100,
  285. right: 100,
  286. width: 100,
  287. height: 100
  288. };
  289. const panelRect = {
  290. width: 50,
  291. height: 100 // !
  292. };
  293. const { top, left } = position( editableRect, panelRect );
  294. expect( top ).to.equal( 150 );
  295. expect( left ).to.equal( expectedLeft );
  296. } );
  297. describe( 'view#viewportTopOffset', () => {
  298. it( 'sticks the panel to the offset when there\'s not enough space above', () => {
  299. view.viewportTopOffset = 50;
  300. const position = view.panelPositions[ positionIndex ];
  301. const editableRect = {
  302. top: 0, // !
  303. bottom: 200,
  304. left: 100,
  305. right: 100,
  306. width: 100,
  307. height: 200
  308. };
  309. const panelRect = {
  310. width: 50,
  311. height: 50
  312. };
  313. const { top, left } = position( editableRect, panelRect );
  314. expect( top ).to.equal( 50 );
  315. expect( left ).to.equal( expectedLeft );
  316. } );
  317. it( 'positions the panel below the editable when there\'s not enough space above/over', () => {
  318. view.viewportTopOffset = 50;
  319. const position = view.panelPositions[ positionIndex ];
  320. const editableRect = {
  321. top: 100,
  322. bottom: 150,
  323. left: 100,
  324. right: 100,
  325. width: 100,
  326. height: 50
  327. };
  328. const panelRect = {
  329. width: 50,
  330. height: 80
  331. };
  332. const { top, left } = position( editableRect, panelRect );
  333. expect( top ).to.equal( 150 );
  334. expect( left ).to.equal( expectedLeft );
  335. } );
  336. } );
  337. }
  338. } );
  339. } );