editor.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* bender-tags: editor, browser-only */
  6. import moduleUtils from '/tests/ckeditor5/_utils/module.js';
  7. import Editor from '/ckeditor5/editor/editor.js';
  8. import Plugin from '/ckeditor5/plugin.js';
  9. import Config from '/ckeditor5/utils/config.js';
  10. import PluginCollection from '/ckeditor5/plugincollection.js';
  11. const pluginClasses = {};
  12. before( () => {
  13. pluginDefinition( 'A/A' );
  14. pluginDefinition( 'B/B' );
  15. pluginDefinition( 'C/C', [ 'B/B' ] );
  16. pluginDefinition( 'D/D', [ 'C/C' ] );
  17. } );
  18. describe( 'Editor', () => {
  19. describe( 'constructor', () => {
  20. it( 'should create a new editor instance', () => {
  21. const editor = new Editor();
  22. expect( editor.config ).to.be.an.instanceof( Config );
  23. expect( editor.commands ).to.be.an.instanceof( Map );
  24. expect( editor.plugins ).to.be.an.instanceof( PluginCollection );
  25. expect( getPlugins( editor ) ).to.be.empty;
  26. } );
  27. } );
  28. describe( 'plugins', () => {
  29. it( 'should be empty on new editor', () => {
  30. const editor = new Editor();
  31. expect( getPlugins( editor ) ).to.be.empty;
  32. } );
  33. } );
  34. describe( 'create', () => {
  35. it( 'should return a promise that resolves properly', () => {
  36. let promise = Editor.create();
  37. expect( promise ).to.be.an.instanceof( Promise );
  38. return promise;
  39. } );
  40. it( 'loads plugins', () => {
  41. return Editor.create( {
  42. features: [ 'A' ]
  43. } )
  44. .then( editor => {
  45. expect( getPlugins( editor ).length ).to.equal( 1 );
  46. expect( editor.plugins.get( 'A' ) ).to.be.an.instanceof( Plugin );
  47. } );
  48. } );
  49. } );
  50. describe( 'initPlugins', () => {
  51. it( 'should load features', () => {
  52. const editor = new Editor( {
  53. features: [ 'A', 'B' ]
  54. } );
  55. expect( getPlugins( editor ) ).to.be.empty;
  56. return editor.initPlugins().then( () => {
  57. expect( getPlugins( editor ).length ).to.equal( 2 );
  58. expect( editor.plugins.get( 'A' ) ).to.be.an.instanceof( Plugin );
  59. expect( editor.plugins.get( 'B' ) ).to.be.an.instanceof( Plugin );
  60. } );
  61. } );
  62. it( 'should load features passed as a string', () => {
  63. const editor = new Editor( {
  64. features: 'A,B'
  65. } );
  66. expect( getPlugins( editor ) ).to.be.empty;
  67. return editor.initPlugins().then( () => {
  68. expect( getPlugins( editor ).length ).to.equal( 2 );
  69. expect( editor.plugins.get( 'A' ) ).to.be.an.instanceof( Plugin );
  70. expect( editor.plugins.get( 'B' ) ).to.be.an.instanceof( Plugin );
  71. } );
  72. } );
  73. it( 'should initialize plugins in the right order', () => {
  74. const editor = new Editor( {
  75. features: [ 'A', 'D' ]
  76. } );
  77. return editor.initPlugins().then( () => {
  78. sinon.assert.callOrder(
  79. editor.plugins.get( pluginClasses[ 'A/A' ] ).init,
  80. editor.plugins.get( pluginClasses[ 'B/B' ] ).init,
  81. editor.plugins.get( pluginClasses[ 'C/C' ] ).init,
  82. editor.plugins.get( pluginClasses[ 'D/D' ] ).init
  83. );
  84. } );
  85. } );
  86. it( 'should initialize plugins in the right order, waiting for asynchronous ones', () => {
  87. class PluginAsync extends Plugin {}
  88. const asyncSpy = sinon.spy().named( 'async-call-spy' );
  89. // Synchronous plugin that depends on an asynchronous one.
  90. pluginDefinition( 'sync/sync', [ 'async/async' ] );
  91. moduleUtils.define( 'async/async', () => {
  92. PluginAsync.prototype.init = sinon.spy( () => {
  93. return new Promise( ( resolve ) => {
  94. setTimeout( () => {
  95. asyncSpy();
  96. resolve();
  97. }, 0 );
  98. } );
  99. } );
  100. return PluginAsync;
  101. } );
  102. const editor = new Editor( {
  103. features: [ 'A', 'sync' ]
  104. } );
  105. return editor.initPlugins().then( () => {
  106. sinon.assert.callOrder(
  107. editor.plugins.get( pluginClasses[ 'A/A' ] ).init,
  108. editor.plugins.get( PluginAsync ).init,
  109. // This one is called with delay by the async init.
  110. asyncSpy,
  111. editor.plugins.get( pluginClasses[ 'sync/sync' ] ).init
  112. );
  113. } );
  114. } );
  115. } );
  116. } );
  117. // @param {String} name Name of the plugin.
  118. // @param {String[]} deps Dependencies of the plugin (only other plugins).
  119. function pluginDefinition( name, deps ) {
  120. moduleUtils.define( name, deps || [], function() {
  121. class NewPlugin extends Plugin {}
  122. NewPlugin.prototype.init = sinon.spy().named( name );
  123. NewPlugin.requires = Array.from( arguments );
  124. pluginClasses[ name ] = NewPlugin;
  125. return NewPlugin;
  126. } );
  127. }
  128. // Returns an array of loaded plugins.
  129. function getPlugins( editor ) {
  130. const plugins = [];
  131. for ( let entry of editor.plugins ) {
  132. // Keep only plugins kept under their classes.
  133. if ( typeof entry[ 0 ] == 'function' ) {
  134. plugins.push( entry[ 1 ] );
  135. }
  136. }
  137. return plugins;
  138. }