8
0

decouplededitorui.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* globals document, Event */
  6. import ComponentFactory from '@ckeditor/ckeditor5-ui/src/componentfactory';
  7. import View from '@ckeditor/ckeditor5-ui/src/view';
  8. import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
  9. import DecoupledEditorUI from '../src/decouplededitorui';
  10. import DecoupledEditorUIView from '../src/decouplededitoruiview';
  11. import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
  12. import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
  13. import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
  14. import utils from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  15. testUtils.createSinonSandbox();
  16. describe( 'DecoupledEditorUI', () => {
  17. let editor, view, ui;
  18. beforeEach( () => {
  19. return VirtualDecoupledTestEditor
  20. .create( {
  21. toolbar: [ 'foo', 'bar' ],
  22. } )
  23. .then( newEditor => {
  24. editor = newEditor;
  25. ui = editor.ui;
  26. view = ui.view;
  27. } );
  28. } );
  29. afterEach( () => {
  30. editor.destroy();
  31. } );
  32. describe( 'constructor()', () => {
  33. it( 'sets #editor', () => {
  34. expect( ui.editor ).to.equal( editor );
  35. } );
  36. it( 'sets #view', () => {
  37. expect( ui.view ).to.be.instanceOf( DecoupledEditorUIView );
  38. } );
  39. it( 'creates #componentFactory factory', () => {
  40. expect( ui.componentFactory ).to.be.instanceOf( ComponentFactory );
  41. } );
  42. it( 'creates #focusTracker', () => {
  43. expect( ui.focusTracker ).to.be.instanceOf( FocusTracker );
  44. } );
  45. } );
  46. describe( 'init()', () => {
  47. it( 'renders the #view', () => {
  48. expect( view.isRendered ).to.be.true;
  49. } );
  50. describe( 'config', () => {
  51. it( 'does nothing if not specified', () => {
  52. expect( view.toolbar.element.parentElement ).to.be.null;
  53. expect( view.editable.element.parentElement ).to.be.null;
  54. } );
  55. describe( 'toolbarContainer', () => {
  56. it( 'allocates view#toolbar (selector)', () => {
  57. return VirtualDecoupledTestEditor
  58. .create( {
  59. toolbar: [ 'foo', 'bar' ],
  60. toolbarContainer: 'body'
  61. } )
  62. .then( newEditor => {
  63. expect( newEditor.ui.view.toolbar.element.parentElement ).to.equal( document.body );
  64. return newEditor;
  65. } )
  66. .then( newEditor => {
  67. newEditor.destroy();
  68. } );
  69. } );
  70. it( 'allocates view#toolbar (element)', () => {
  71. return VirtualDecoupledTestEditor
  72. .create( {
  73. toolbar: [ 'foo', 'bar' ],
  74. toolbarContainer: document.body
  75. } )
  76. .then( newEditor => {
  77. expect( newEditor.ui.view.toolbar.element.parentElement ).to.equal( document.body );
  78. return newEditor;
  79. } )
  80. .then( newEditor => {
  81. newEditor.destroy();
  82. } );
  83. } );
  84. } );
  85. describe( 'editableContainer', () => {
  86. it( 'allocates view#toolbar (selector)', () => {
  87. return VirtualDecoupledTestEditor
  88. .create( {
  89. toolbar: [ 'foo', 'bar' ],
  90. editableContainer: 'body'
  91. } )
  92. .then( newEditor => {
  93. expect( newEditor.ui.view.editable.element.parentElement ).to.equal( document.body );
  94. return newEditor;
  95. } )
  96. .then( newEditor => {
  97. newEditor.destroy();
  98. } );
  99. } );
  100. it( 'allocates view#toolbar (element)', () => {
  101. return VirtualDecoupledTestEditor
  102. .create( {
  103. toolbar: [ 'foo', 'bar' ],
  104. editableContainer: document.body
  105. } )
  106. .then( newEditor => {
  107. expect( newEditor.ui.view.editable.element.parentElement ).to.equal( document.body );
  108. return newEditor;
  109. } )
  110. .then( newEditor => {
  111. newEditor.destroy();
  112. } );
  113. } );
  114. } );
  115. } );
  116. describe( 'editable', () => {
  117. it( 'registers view.editable#element in editor focus tracker', () => {
  118. ui.focusTracker.isFocused = false;
  119. view.editable.element.dispatchEvent( new Event( 'focus' ) );
  120. expect( ui.focusTracker.isFocused ).to.true;
  121. } );
  122. it( 'sets view.editable#name', () => {
  123. const editable = editor.editing.view.document.getRoot();
  124. expect( view.editable.name ).to.equal( editable.rootName );
  125. } );
  126. it( 'binds view.editable#isFocused', () => {
  127. utils.assertBinding(
  128. view.editable,
  129. { isFocused: false },
  130. [
  131. [ editor.editing.view.document, { isFocused: true } ]
  132. ],
  133. { isFocused: true }
  134. );
  135. } );
  136. it( 'binds view.editable#isReadOnly', () => {
  137. const editable = editor.editing.view.document.getRoot();
  138. utils.assertBinding(
  139. view.editable,
  140. { isReadOnly: false },
  141. [
  142. [ editable, { isReadOnly: true } ]
  143. ],
  144. { isReadOnly: true }
  145. );
  146. } );
  147. } );
  148. describe( 'view.toolbar#items', () => {
  149. it( 'are filled with the config.toolbar (specified as an Array)', () => {
  150. return VirtualDecoupledTestEditor
  151. .create( {
  152. toolbar: [ 'foo', 'bar' ]
  153. } )
  154. .then( editor => {
  155. const items = editor.ui.view.toolbar.items;
  156. expect( items.get( 0 ).name ).to.equal( 'foo' );
  157. expect( items.get( 1 ).name ).to.equal( 'bar' );
  158. return editor.destroy();
  159. } );
  160. } );
  161. it( 'are filled with the config.toolbar (specified as an Object)', () => {
  162. return VirtualDecoupledTestEditor
  163. .create( {
  164. toolbar: {
  165. items: [ 'foo', 'bar' ],
  166. viewportTopOffset: 100
  167. }
  168. } )
  169. .then( editor => {
  170. const items = editor.ui.view.toolbar.items;
  171. expect( items.get( 0 ).name ).to.equal( 'foo' );
  172. expect( items.get( 1 ).name ).to.equal( 'bar' );
  173. return editor.destroy();
  174. } );
  175. } );
  176. } );
  177. it( 'initializes keyboard navigation between view#toolbar and view#editable', () => {
  178. return VirtualDecoupledTestEditor.create()
  179. .then( editor => {
  180. const ui = editor.ui;
  181. const view = ui.view;
  182. const spy = testUtils.sinon.spy( view.toolbar, 'focus' );
  183. ui.focusTracker.isFocused = true;
  184. ui.view.toolbar.focusTracker.isFocused = false;
  185. editor.keystrokes.press( {
  186. keyCode: keyCodes.f10,
  187. altKey: true,
  188. preventDefault: sinon.spy(),
  189. stopPropagation: sinon.spy()
  190. } );
  191. sinon.assert.calledOnce( spy );
  192. return editor.destroy();
  193. } );
  194. } );
  195. } );
  196. describe( 'destroy()', () => {
  197. it( 'destroys the #view', () => {
  198. const spy = sinon.spy( view, 'destroy' );
  199. return editor.destroy()
  200. .then( () => {
  201. sinon.assert.calledOnce( spy );
  202. sinon.assert.calledWithExactly( spy, false, false );
  203. } );
  204. } );
  205. it( 'removes view#toolbar from DOM, if config.toolbarContainer is specified', () => {
  206. let spy;
  207. return VirtualDecoupledTestEditor
  208. .create( {
  209. toolbar: [ 'foo', 'bar' ],
  210. toolbarContainer: 'body'
  211. } )
  212. .then( newEditor => {
  213. spy = sinon.spy( newEditor.ui.view, 'destroy' );
  214. newEditor.destroy();
  215. } )
  216. .then( () => {
  217. sinon.assert.calledWithExactly( spy, true, false );
  218. } );
  219. } );
  220. it( 'removes view#editable from DOM, if config.editableContainer is specified', () => {
  221. let spy;
  222. return VirtualDecoupledTestEditor
  223. .create( {
  224. toolbar: [ 'foo', 'bar' ],
  225. editableContainer: 'body'
  226. } )
  227. .then( newEditor => {
  228. spy = sinon.spy( newEditor.ui.view, 'destroy' );
  229. newEditor.destroy();
  230. } )
  231. .then( () => {
  232. sinon.assert.calledWithExactly( spy, false, true );
  233. } );
  234. } );
  235. } );
  236. } );
  237. function viewCreator( name ) {
  238. return locale => {
  239. const view = new View( locale );
  240. view.name = name;
  241. view.element = document.createElement( 'a' );
  242. return view;
  243. };
  244. }
  245. class VirtualDecoupledTestEditor extends VirtualTestEditor {
  246. constructor( config ) {
  247. super( config );
  248. const view = new DecoupledEditorUIView( this.locale );
  249. this.ui = new DecoupledEditorUI( this, view );
  250. this.ui.componentFactory.add( 'foo', viewCreator( 'foo' ) );
  251. this.ui.componentFactory.add( 'bar', viewCreator( 'bar' ) );
  252. }
  253. destroy() {
  254. this.ui.destroy();
  255. return super.destroy();
  256. }
  257. static create( config ) {
  258. return new Promise( resolve => {
  259. const editor = new this( config );
  260. resolve(
  261. editor.initPlugins()
  262. .then( () => {
  263. editor.ui.init();
  264. editor.fire( 'uiReady' );
  265. editor.fire( 'dataReady' );
  266. editor.fire( 'ready' );
  267. } )
  268. .then( () => editor )
  269. );
  270. } );
  271. }
  272. }