ballooneditorui.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. /**
  2. * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* globals document, Event */
  6. import BalloonEditorUI from '../src/ballooneditorui';
  7. import EditorUI from '@ckeditor/ckeditor5-core/src/editor/editorui';
  8. import BalloonEditorUIView from '../src/ballooneditoruiview';
  9. import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
  10. import BalloonToolbar from '@ckeditor/ckeditor5-ui/src/toolbar/balloon/balloontoolbar';
  11. import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
  12. import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
  13. import { isElement } from 'lodash-es';
  14. import utils from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  15. import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
  16. describe( 'BalloonEditorUI', () => {
  17. let editor, view, ui, viewElement;
  18. testUtils.createSinonSandbox();
  19. beforeEach( () => {
  20. return VirtualBalloonTestEditor
  21. .create( 'foo', {
  22. plugins: [ BalloonToolbar ]
  23. } )
  24. .then( newEditor => {
  25. editor = newEditor;
  26. ui = editor.ui;
  27. view = ui.view;
  28. viewElement = view.editable.element;
  29. } );
  30. } );
  31. afterEach( () => {
  32. editor.destroy();
  33. } );
  34. describe( 'constructor()', () => {
  35. it( 'extends EditorUI', () => {
  36. expect( ui ).to.instanceof( EditorUI );
  37. } );
  38. } );
  39. describe( 'init()', () => {
  40. it( 'initializes the #view', () => {
  41. expect( view.isRendered ).to.be.true;
  42. } );
  43. it( 'initializes keyboard navigation between view#toolbar and view#editable', () => {
  44. const toolbar = editor.plugins.get( 'BalloonToolbar' );
  45. const toolbarFocusSpy = testUtils.sinon.stub( toolbar.toolbarView, 'focus' ).returns( {} );
  46. const toolbarShowSpy = testUtils.sinon.stub( toolbar, 'show' ).returns( {} );
  47. const toolbarHideSpy = testUtils.sinon.stub( toolbar, 'hide' ).returns( {} );
  48. const editingFocusSpy = testUtils.sinon.stub( editor.editing.view, 'focus' ).returns( {} );
  49. ui.focusTracker.isFocused = true;
  50. // #show and #hide are mocked so mocking the focus as well.
  51. toolbar.toolbarView.focusTracker.isFocused = false;
  52. editor.keystrokes.press( {
  53. keyCode: keyCodes.f10,
  54. altKey: true,
  55. preventDefault: sinon.spy(),
  56. stopPropagation: sinon.spy()
  57. } );
  58. sinon.assert.callOrder( toolbarShowSpy, toolbarFocusSpy );
  59. sinon.assert.notCalled( toolbarHideSpy );
  60. sinon.assert.notCalled( editingFocusSpy );
  61. // #show and #hide are mocked so mocking the focus as well.
  62. toolbar.toolbarView.focusTracker.isFocused = true;
  63. toolbar.toolbarView.keystrokes.press( {
  64. keyCode: keyCodes.esc,
  65. preventDefault: sinon.spy(),
  66. stopPropagation: sinon.spy()
  67. } );
  68. sinon.assert.callOrder( editingFocusSpy, toolbarHideSpy );
  69. } );
  70. describe( 'editable', () => {
  71. let editable;
  72. beforeEach( () => {
  73. editable = editor.editing.view.document.getRoot();
  74. } );
  75. it( 'registers view.editable#element in editor focus tracker', () => {
  76. ui.focusTracker.isFocused = false;
  77. view.editable.element.dispatchEvent( new Event( 'focus' ) );
  78. expect( ui.focusTracker.isFocused ).to.true;
  79. } );
  80. it( 'sets view.editable#name', () => {
  81. expect( view.editable.name ).to.equal( editable.rootName );
  82. } );
  83. it( 'binds view.editable#isFocused', () => {
  84. utils.assertBinding(
  85. view.editable,
  86. { isFocused: false },
  87. [
  88. [ ui.focusTracker, { isFocused: true } ]
  89. ],
  90. { isFocused: true }
  91. );
  92. } );
  93. } );
  94. describe( 'placeholder', () => {
  95. it( 'sets placeholder from editor.config.placeholder', () => {
  96. return VirtualBalloonTestEditor
  97. .create( 'foo', {
  98. extraPlugins: [ BalloonToolbar, Paragraph ],
  99. placeholder: 'placeholder-text',
  100. } )
  101. .then( newEditor => {
  102. editor = newEditor;
  103. const firstChild = editor.editing.view.document.getRoot().getChild( 0 );
  104. expect( firstChild.getAttribute( 'data-placeholder' ) ).to.equal( 'placeholder-text' );
  105. return editor.destroy();
  106. } );
  107. } );
  108. it( 'sets placeholder from "placeholder" attribute of a passed element', () => {
  109. const element = document.createElement( 'div' );
  110. element.setAttribute( 'placeholder', 'placeholder-text' );
  111. return VirtualBalloonTestEditor
  112. .create( element, {
  113. extraPlugins: [ BalloonToolbar, Paragraph ]
  114. } )
  115. .then( newEditor => {
  116. editor = newEditor;
  117. const firstChild = editor.editing.view.document.getRoot().getChild( 0 );
  118. expect( firstChild.getAttribute( 'data-placeholder' ) ).to.equal( 'placeholder-text' );
  119. return editor.destroy();
  120. } );
  121. } );
  122. it( 'uses editor.config.placeholder rather than "placeholder" attribute of a passed element', () => {
  123. const element = document.createElement( 'div' );
  124. element.setAttribute( 'placeholder', 'placeholder-text' );
  125. return VirtualBalloonTestEditor
  126. .create( element, {
  127. placeholder: 'config takes precedence',
  128. extraPlugins: [ BalloonToolbar, Paragraph ]
  129. } )
  130. .then( newEditor => {
  131. editor = newEditor;
  132. const firstChild = editor.editing.view.document.getRoot().getChild( 0 );
  133. expect( firstChild.getAttribute( 'data-placeholder' ) ).to.equal( 'config takes precedence' );
  134. return editor.destroy();
  135. } );
  136. } );
  137. } );
  138. } );
  139. describe( 'element()', () => {
  140. it( 'returns correct element instance', () => {
  141. expect( ui.element ).to.equal( viewElement );
  142. } );
  143. } );
  144. describe( 'getEditableElement()', () => {
  145. it( 'returns editable element (default)', () => {
  146. expect( ui.getEditableElement() ).to.equal( view.editable.element );
  147. } );
  148. it( 'returns editable element (root name passed)', () => {
  149. expect( ui.getEditableElement( 'main' ) ).to.equal( view.editable.element );
  150. } );
  151. it( 'returns undefined if editable with the given name is absent', () => {
  152. expect( ui.getEditableElement( 'absent' ) ).to.be.undefined;
  153. } );
  154. } );
  155. } );
  156. class VirtualBalloonTestEditor extends VirtualTestEditor {
  157. constructor( sourceElementOrData, config ) {
  158. super( config );
  159. if ( isElement( sourceElementOrData ) ) {
  160. this.sourceElement = sourceElementOrData;
  161. }
  162. const view = new BalloonEditorUIView( this.locale, this.editing.view );
  163. this.ui = new BalloonEditorUI( this, view );
  164. }
  165. destroy() {
  166. this.ui.destroy();
  167. return super.destroy();
  168. }
  169. static create( sourceElementOrData, config ) {
  170. return new Promise( resolve => {
  171. const editor = new this( sourceElementOrData, config );
  172. resolve(
  173. editor.initPlugins()
  174. .then( () => {
  175. editor.ui.init();
  176. editor.fire( 'ready' );
  177. } )
  178. .then( () => editor )
  179. );
  180. } );
  181. }
  182. }