| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 |
- /**
- * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
- /* globals document, setTimeout, window */
- import ContextWatchdog from '../src/contextwatchdog';
- import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
- import Context from '@ckeditor/ckeditor5-core/src/context';
- import sinon from 'sinon';
- import { expect } from 'chai';
- import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
- describe( 'ContextWatchdog', () => {
- let element1, element2;
- let mainWatchdog;
- let originalErrorHandler;
- beforeEach( () => {
- element1 = document.createElement( 'div' );
- element2 = document.createElement( 'div' );
- document.body.appendChild( element1 );
- document.body.appendChild( element2 );
- originalErrorHandler = window.onerror;
- window.onerror = sinon.spy();
- } );
- afterEach( () => {
- window.onerror = originalErrorHandler;
- element1.remove();
- element2.remove();
- sinon.restore();
- } );
- it( 'should disable adding items once the Watchdog is destroyed', async () => {
- mainWatchdog = ContextWatchdog.for( Context, {} );
- await mainWatchdog.destroy();
- let err;
- try {
- await mainWatchdog.add( {
- editor2: {
- type: 'editor',
- creator: ( el, config ) => ClassicTestEditor.create( el, config ),
- sourceElementOrData: element1,
- config: {}
- },
- } );
- } catch ( _err ) {
- err = _err;
- }
- expect( err ).to.be.instanceOf( Error );
- expect( err.message ).to.match( /Cannot add items do destroyed watchdog\./ );
- } );
- it.skip( 'case: editor, contextItem', async () => {
- mainWatchdog = ContextWatchdog.for( Context, {} );
- mainWatchdog.add( {
- editor1: {
- type: 'editor',
- creator: ( el, config ) => ClassicTestEditor.create( el, config ),
- sourceElementOrData: element1,
- config: {}
- },
- annotatedInput: {
- type: 'contextItem',
- creator: () => { }
- }
- } );
- await mainWatchdog.waitForReady();
- await mainWatchdog.destroy();
- } );
- describe( 'for scenario with no items', () => {
- it( 'should create only context', async () => {
- mainWatchdog = ContextWatchdog.for( Context, {} );
- await mainWatchdog.waitForReady();
- expect( mainWatchdog.context ).to.be.instanceOf( Context );
- await mainWatchdog.destroy();
- } );
- it( 'should have proper states', async () => {
- mainWatchdog = ContextWatchdog.for( Context, {} );
- expect( mainWatchdog.state ).to.equal( 'initializing' );
- await mainWatchdog.waitForReady();
- expect( mainWatchdog.state ).to.equal( 'ready' );
- await mainWatchdog.destroy();
- expect( mainWatchdog.state ).to.equal( 'destroyed' );
- } );
- it( 'should set custom destructor if provided', async () => {
- const mainWatchdog = new ContextWatchdog();
- const customDestructor = sinon.spy( context => context.destroy() );
- mainWatchdog.setCreator( config => Context.create( config ) );
- mainWatchdog.setDestructor( customDestructor );
- mainWatchdog._create();
- await mainWatchdog.destroy();
- sinon.assert.calledOnce( customDestructor );
- } );
- describe( 'in case of error handling', () => {
- it( 'should restart the `Context`', async () => {
- mainWatchdog = ContextWatchdog.for( Context, {} );
- const errorSpy = sinon.spy();
- await mainWatchdog.waitForReady();
- const oldContext = mainWatchdog.context;
- mainWatchdog.on( 'restart', errorSpy );
- setTimeout( () => throwCKEditorError( 'foo', mainWatchdog.context ) );
- await waitCycle();
- sinon.assert.calledOnce( errorSpy );
- expect( mainWatchdog.context ).to.not.equal( oldContext );
- } );
- } );
- } );
- describe( 'for multiple items scenario', () => {
- it( 'should allow adding multiple items without waiting for promises', async () => {
- mainWatchdog = ContextWatchdog.for( Context, {} );
- mainWatchdog.add( {
- editor1: {
- type: 'editor',
- creator: ( el, config ) => ClassicTestEditor.create( el, config ),
- sourceElementOrData: element1,
- config: {}
- },
- } );
- mainWatchdog.add( {
- editor2: {
- type: 'editor',
- creator: ( el, config ) => ClassicTestEditor.create( el, config ),
- sourceElementOrData: element2,
- config: {}
- },
- } );
- await mainWatchdog.waitForReady();
- await mainWatchdog.destroy();
- } );
- it( 'should throw when multiple items with the same name are added', async () => {
- mainWatchdog = ContextWatchdog.for( Context, {} );
- mainWatchdog.add( {
- editor1: {
- type: 'editor',
- creator: ( el, config ) => ClassicTestEditor.create( el, config ),
- sourceElementOrData: element1,
- config: {}
- },
- } );
- await mainWatchdog.waitForReady();
- let err;
- try {
- await mainWatchdog.add( {
- editor1: {
- type: 'editor',
- creator: ( el, config ) => ClassicTestEditor.create( el, config ),
- sourceElementOrData: element1,
- config: {}
- },
- } );
- } catch ( _err ) {
- err = _err;
- }
- mainWatchdog._actionQueue.clear();
- await mainWatchdog.destroy();
- expect( err ).to.be.instanceOf( Error );
- expect( err.message ).to.match( /Watchdog with the given name is already added: 'editor1'./ );
- } );
- it( 'should throw when not added item is removed', async () => {
- mainWatchdog = ContextWatchdog.for( Context, {} );
- await mainWatchdog.waitForReady();
- let err;
- try {
- await mainWatchdog.remove( [ 'foo' ] );
- } catch ( _err ) {
- err = _err;
- }
- mainWatchdog._actionQueue.clear();
- await mainWatchdog.destroy();
- expect( err ).to.be.instanceOf( Error );
- expect( err.message ).to.match( /There is no watchdog named: 'foo'./ );
- } );
- it( 'should allow setting editor custom destructors', async () => {
- mainWatchdog = ContextWatchdog.for( Context, {} );
- const destructorSpy = sinon.spy( editor => editor.destroy() );
- mainWatchdog.add( {
- editor1: {
- type: 'editor',
- creator: ( el, config ) => ClassicTestEditor.create( el, config ),
- destructor: destructorSpy,
- sourceElementOrData: element1,
- config: {},
- },
- } );
- await mainWatchdog.destroy();
- sinon.assert.calledOnce( destructorSpy );
- } );
- it( 'should throw when the item is of not known type', async () => {
- mainWatchdog = ContextWatchdog.for( Context, {} );
- await mainWatchdog.waitForReady();
- let err;
- try {
- await mainWatchdog.add( {
- editor1: {
- type: 'foo',
- creator: ( el, config ) => ClassicTestEditor.create( el, config ),
- sourceElementOrData: element1,
- config: {}
- },
- } );
- } catch ( _err ) {
- mainWatchdog._stopErrorHandling();
- err = _err;
- }
- mainWatchdog._actionQueue.clear();
- await mainWatchdog.destroy();
- expect( err ).to.be.instanceOf( Error );
- expect( err.message ).to.match( /Not supported item type: 'foo'\./ );
- } );
- it( 'should allow adding and removing items without waiting', async () => {
- mainWatchdog = ContextWatchdog.for( Context, {} );
- mainWatchdog.add( {
- editor1: {
- type: 'editor',
- creator: ( el, config ) => ClassicTestEditor.create( el, config ),
- sourceElementOrData: element1,
- config: {}
- },
- } );
- mainWatchdog.add( {
- editor2: {
- type: 'editor',
- creator: ( el, config ) => ClassicTestEditor.create( el, config ),
- sourceElementOrData: element2,
- config: {}
- },
- } );
- await mainWatchdog.waitForReady();
- expect( mainWatchdog.state ).to.equal( 'ready' );
- mainWatchdog.remove( [ 'editor1' ] );
- await mainWatchdog.waitForReady();
- mainWatchdog.remove( [ 'editor2' ] );
- await mainWatchdog.waitForReady();
- await mainWatchdog.destroy();
- } );
- it( 'should not change the input items', async () => {
- mainWatchdog = ContextWatchdog.for( Context, {} );
- mainWatchdog.add( Object.freeze( {
- editor1: {
- type: 'editor',
- creator: ( el, config ) => ClassicTestEditor.create( el, config ),
- sourceElementOrData: element1,
- config: {}
- }
- } ) );
- mainWatchdog._restart();
- await mainWatchdog.waitForReady();
- await mainWatchdog.destroy();
- } );
- describe( 'in case of error handling', () => {
- it( 'should restart the whole structure of editors if an error happens inside the `Context`', async () => {
- mainWatchdog = ContextWatchdog.for( Context, {} );
- mainWatchdog.add( {
- editor1: {
- type: 'editor',
- creator: ( el, config ) => ClassicTestEditor.create( el, config ),
- sourceElementOrData: element1,
- config: {}
- }
- } );
- await mainWatchdog.waitForReady();
- const oldContext = mainWatchdog.context;
- const restartSpy = sinon.spy();
- mainWatchdog.on( 'restart', restartSpy );
- setTimeout( () => throwCKEditorError( 'foo', mainWatchdog.context ) );
- await waitCycle();
- sinon.assert.calledOnce( restartSpy );
- expect( mainWatchdog._watchdogs.get( 'editor1' ).state ).to.equal( 'ready' );
- expect( mainWatchdog.context ).to.not.equal( oldContext );
- await mainWatchdog.destroy();
- } );
- it( 'should restart only the editor if an error happens inside the editor', async () => {
- mainWatchdog = ContextWatchdog.for( Context, {} );
- mainWatchdog.add( {
- editor1: {
- type: 'editor',
- creator: ( el, config ) => ClassicTestEditor.create( el, config ),
- sourceElementOrData: element1,
- config: {}
- }
- } );
- await mainWatchdog.waitForReady();
- const oldContext = mainWatchdog.context;
- const restartSpy = sinon.spy();
- const oldEditor = mainWatchdog.getWatchdog( 'editor1' ).editor;
- mainWatchdog.on( 'restart', restartSpy );
- setTimeout( () => throwCKEditorError( 'foo', oldEditor ) );
- await waitCycle();
- sinon.assert.notCalled( restartSpy );
- expect( mainWatchdog.context ).to.equal( oldContext );
- expect( mainWatchdog.getWatchdog( 'editor1' ).editor ).to.not.equal( oldEditor );
- expect( mainWatchdog.getWatchdog( 'editor1' ).state ).to.equal( 'ready' );
- await mainWatchdog.destroy();
- } );
- it( 'should restart only the editor if an error happens inside one of the editors', async () => {
- mainWatchdog = ContextWatchdog.for( Context, {} );
- mainWatchdog.add( {
- editor1: {
- type: 'editor',
- creator: ( el, config ) => ClassicTestEditor.create( el, config ),
- sourceElementOrData: element1,
- config: {}
- },
- editor2: {
- type: 'editor',
- creator: ( el, config ) => ClassicTestEditor.create( el, config ),
- sourceElementOrData: element1,
- config: {}
- }
- } );
- await mainWatchdog.waitForReady();
- const oldContext = mainWatchdog.context;
- const editorWatchdog1 = mainWatchdog.getWatchdog( 'editor1' );
- const editorWatchdog2 = mainWatchdog.getWatchdog( 'editor2' );
- const oldEditor1 = editorWatchdog1.editor;
- const oldEditor2 = editorWatchdog2.editor;
- const mainWatchdogRestartSpy = sinon.spy();
- const editorWatchdog1RestartSpy = sinon.spy();
- const editorWatchdog2RestartSpy = sinon.spy();
- mainWatchdog.on( 'restart', mainWatchdogRestartSpy );
- editorWatchdog1.on( 'restart', editorWatchdog1RestartSpy );
- editorWatchdog2.on( 'restart', editorWatchdog2RestartSpy );
- setTimeout( () => throwCKEditorError( 'foo', editorWatchdog1.editor ) );
- await waitCycle();
- sinon.assert.calledOnce( editorWatchdog1RestartSpy );
- sinon.assert.notCalled( mainWatchdogRestartSpy );
- sinon.assert.notCalled( editorWatchdog2RestartSpy );
- expect( editorWatchdog1.state ).to.equal( 'ready' );
- expect( editorWatchdog2.state ).to.equal( 'ready' );
- expect( mainWatchdog.state ).to.equal( 'ready' );
- expect( oldEditor1 ).to.not.equal( editorWatchdog1.editor );
- expect( oldEditor2 ).to.equal( editorWatchdog2.editor );
- expect( mainWatchdog.context ).to.equal( oldContext );
- await mainWatchdog.destroy();
- } );
- } );
- } );
- } );
- function throwCKEditorError( name, context ) {
- throw new CKEditorError( name, context );
- }
- function waitCycle() {
- return new Promise( res => setTimeout( res ) );
- }
|