contextwatchdog.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  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. /* globals document, setTimeout, window */
  6. import ContextWatchdog from '../src/contextwatchdog';
  7. import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
  8. import Context from '@ckeditor/ckeditor5-core/src/context';
  9. import sinon from 'sinon';
  10. import { expect } from 'chai';
  11. import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
  12. describe( 'ContextWatchdog', () => {
  13. let element1, element2;
  14. let mainWatchdog;
  15. beforeEach( () => {
  16. element1 = document.createElement( 'div' );
  17. element2 = document.createElement( 'div' );
  18. document.body.appendChild( element1 );
  19. document.body.appendChild( element2 );
  20. } );
  21. afterEach( () => {
  22. element1.remove();
  23. element2.remove();
  24. sinon.restore();
  25. } );
  26. it( 'should disable adding items once the Watchdog is destroyed', async () => {
  27. mainWatchdog = ContextWatchdog.for( Context, {} );
  28. await mainWatchdog.destroy();
  29. let err;
  30. try {
  31. await mainWatchdog.add( {
  32. editor2: {
  33. type: 'editor',
  34. creator: ( el, config ) => ClassicTestEditor.create( el, config ),
  35. sourceElementOrData: element1,
  36. config: {}
  37. },
  38. } );
  39. } catch ( _err ) {
  40. err = _err;
  41. }
  42. expect( err ).to.be.instanceOf( Error );
  43. expect( err.message ).to.match( /Cannot add items do destroyed watchdog\./ );
  44. } );
  45. it.skip( 'case: editor, contextItem', async () => {
  46. mainWatchdog = ContextWatchdog.for( Context, {} );
  47. mainWatchdog.add( {
  48. editor1: {
  49. type: 'editor',
  50. creator: ( el, config ) => ClassicTestEditor.create( el, config ),
  51. sourceElementOrData: element1,
  52. config: {}
  53. },
  54. annotatedInput: {
  55. type: 'contextItem',
  56. creator: () => { }
  57. }
  58. } );
  59. await mainWatchdog.waitForReady();
  60. await mainWatchdog.destroy();
  61. } );
  62. describe( 'for no items added', () => {
  63. it( 'should create only context', async () => {
  64. mainWatchdog = ContextWatchdog.for( Context, {} );
  65. await mainWatchdog.waitForReady();
  66. expect( mainWatchdog.context ).to.be.instanceOf( Context );
  67. await mainWatchdog.destroy();
  68. } );
  69. it( 'should have proper states', async () => {
  70. mainWatchdog = ContextWatchdog.for( Context, {} );
  71. expect( mainWatchdog.state ).to.equal( 'initializing' );
  72. await mainWatchdog.waitForReady();
  73. expect( mainWatchdog.state ).to.equal( 'ready' );
  74. await mainWatchdog.destroy();
  75. expect( mainWatchdog.state ).to.equal( 'destroyed' );
  76. } );
  77. it( 'should set custom destructor if provided', async () => {
  78. const mainWatchdog = new ContextWatchdog();
  79. const customDestructor = sinon.spy( context => context.destroy() );
  80. mainWatchdog.setCreator( config => Context.create( config ) );
  81. mainWatchdog.setDestructor( customDestructor );
  82. mainWatchdog._create();
  83. await mainWatchdog.destroy();
  84. sinon.assert.calledOnce( customDestructor );
  85. } );
  86. describe( 'in case of error handling', () => {
  87. it( 'should restart the `Context`', async () => {
  88. mainWatchdog = ContextWatchdog.for( Context, {} );
  89. const errorSpy = sinon.spy();
  90. await mainWatchdog.waitForReady();
  91. const oldContext = mainWatchdog.context;
  92. const originalErrorHandler = window.onerror;
  93. window.onerror = sinon.spy();
  94. mainWatchdog.on( 'restart', errorSpy );
  95. setTimeout( () => throwCKEditorError( 'foo', mainWatchdog.context ) );
  96. await waitCycle();
  97. sinon.assert.calledOnce( errorSpy );
  98. expect( mainWatchdog.context ).to.not.equal( oldContext );
  99. window.onerror = originalErrorHandler;
  100. } );
  101. } );
  102. } );
  103. describe( 'for multiple editors', () => {
  104. it( 'should allow adding multiple items without waiting', async () => {
  105. mainWatchdog = ContextWatchdog.for( Context, {} );
  106. mainWatchdog.add( {
  107. editor1: {
  108. type: 'editor',
  109. creator: ( el, config ) => ClassicTestEditor.create( el, config ),
  110. sourceElementOrData: element1,
  111. config: {}
  112. },
  113. } );
  114. mainWatchdog.add( {
  115. editor2: {
  116. type: 'editor',
  117. creator: ( el, config ) => ClassicTestEditor.create( el, config ),
  118. sourceElementOrData: element2,
  119. config: {}
  120. },
  121. } );
  122. await mainWatchdog.waitForReady();
  123. await mainWatchdog.destroy();
  124. } );
  125. it( 'should throw when multiple items with the same name are added', async () => {
  126. mainWatchdog = ContextWatchdog.for( Context, {} );
  127. mainWatchdog.add( {
  128. editor1: {
  129. type: 'editor',
  130. creator: ( el, config ) => ClassicTestEditor.create( el, config ),
  131. sourceElementOrData: element1,
  132. config: {}
  133. },
  134. } );
  135. await mainWatchdog.waitForReady();
  136. let err;
  137. try {
  138. await mainWatchdog.add( {
  139. editor1: {
  140. type: 'editor',
  141. creator: ( el, config ) => ClassicTestEditor.create( el, config ),
  142. sourceElementOrData: element1,
  143. config: {}
  144. },
  145. } );
  146. } catch ( _err ) {
  147. err = _err;
  148. }
  149. mainWatchdog._actionQueue.clear();
  150. await mainWatchdog.destroy();
  151. expect( err ).to.be.instanceOf( Error );
  152. expect( err.message ).to.match( /Watchdog with the given name is already added: 'editor1'./ );
  153. } );
  154. it( 'should throw when the item is of not known type', async () => {
  155. mainWatchdog = ContextWatchdog.for( Context, {} );
  156. await mainWatchdog.waitForReady();
  157. let err;
  158. try {
  159. await mainWatchdog.add( {
  160. editor1: {
  161. type: 'foo',
  162. creator: ( el, config ) => ClassicTestEditor.create( el, config ),
  163. sourceElementOrData: element1,
  164. config: {}
  165. },
  166. } );
  167. } catch ( _err ) {
  168. mainWatchdog._stopErrorHandling();
  169. err = _err;
  170. }
  171. mainWatchdog._actionQueue.clear();
  172. await mainWatchdog.destroy();
  173. expect( err ).to.be.instanceOf( Error );
  174. expect( err.message ).to.match( /Not supported item type: 'foo'\./ );
  175. } );
  176. it( 'should allow adding and removing items without waiting', async () => {
  177. mainWatchdog = ContextWatchdog.for( Context, {} );
  178. mainWatchdog.add( {
  179. editor1: {
  180. type: 'editor',
  181. creator: ( el, config ) => ClassicTestEditor.create( el, config ),
  182. sourceElementOrData: element1,
  183. config: {}
  184. },
  185. } );
  186. mainWatchdog.add( {
  187. editor2: {
  188. type: 'editor',
  189. creator: ( el, config ) => ClassicTestEditor.create( el, config ),
  190. sourceElementOrData: element2,
  191. config: {}
  192. },
  193. } );
  194. await mainWatchdog.waitForReady();
  195. expect( mainWatchdog.state ).to.equal( 'ready' );
  196. mainWatchdog.remove( [ 'editor1' ] );
  197. await mainWatchdog.waitForReady();
  198. mainWatchdog.remove( [ 'editor2' ] );
  199. await mainWatchdog.waitForReady();
  200. await mainWatchdog.destroy();
  201. } );
  202. describe( 'in case of error handling', () => {
  203. it( 'should restart the whole structure of editors if an error happens inside the `Context`', async () => {
  204. mainWatchdog = ContextWatchdog.for( Context, {} );
  205. mainWatchdog.add( {
  206. editor1: {
  207. type: 'editor',
  208. creator: ( el, config ) => ClassicTestEditor.create( el, config ),
  209. sourceElementOrData: element1,
  210. config: {}
  211. }
  212. } );
  213. await mainWatchdog.waitForReady();
  214. const oldContext = mainWatchdog.context;
  215. const restartSpy = sinon.spy();
  216. const originalErrorHandler = window.onerror;
  217. window.onerror = sinon.spy();
  218. mainWatchdog.on( 'restart', restartSpy );
  219. setTimeout( () => throwCKEditorError( 'foo', mainWatchdog.context ) );
  220. await waitCycle();
  221. sinon.assert.calledOnce( restartSpy );
  222. expect( mainWatchdog.context ).to.not.equal( oldContext );
  223. window.onerror = originalErrorHandler;
  224. } );
  225. } );
  226. } );
  227. } );
  228. function throwCKEditorError( name, context ) {
  229. throw new CKEditorError( name, context );
  230. }
  231. function waitCycle() {
  232. return new Promise( res => setTimeout( res ) );
  233. }