Explorar el Código

Added simple tests for error handling in the Context.

Maciej Bukowski hace 5 años
padre
commit
8febbda660

+ 31 - 11
packages/ckeditor5-watchdog/src/contextwatchdog.js

@@ -11,7 +11,8 @@
 
 import Watchdog from './watchdog';
 import EditorWatchdog from './editorwatchdog';
-import areConnectedThroughProperties from '@ckeditor/ckeditor5-utils/src/areconnectedthroughproperties';
+import areConnectedThroughProperties from './utils/areconnectedthroughproperties';
+import getSubNodes from './utils/getsubnodes';
 
 export default class ContextWatchdog extends Watchdog {
 	/**
@@ -21,23 +22,41 @@ export default class ContextWatchdog extends Watchdog {
 		super( config );
 
 		/**
+		 * @protected
 		 * @type {Map.<string,EditorWatchdog>}
 		 */
 		this._watchdogs = new Map();
 
-		/** @type {Context|null} */
+		/**
+		 * @private
+		 * @type {Context|null}
+		 */
 		this._context = null;
 
 		/**
-		 * The context configuration.
-		 *
 		 * @private
-		 * @member {Object|undefined} #_config
+		 * @type {Set.<*>|undefined}
 		 */
+		this._contextProps;
 
+		/**
+		 * @private
+		 * @type {ActionQueue}
+		 */
 		this._actionQueue = new ActionQueue();
 
+		/**
+		 * @private
+		 * @type {Object}
+		 */
 		this._contextConfig = contextConfig;
+
+		/**
+		 * The context configuration.
+		 *
+		 * @private
+		 * @member {Object|undefined} #_config
+		 */
 	}
 
 	/**
@@ -136,7 +155,10 @@ export default class ContextWatchdog extends Watchdog {
 	 */
 	async _create( isInternal = false ) {
 		await this._actionQueue.enqueue( async () => {
+			this._startErrorHandling();
+
 			this._context = await this._creator( this._contextConfig );
+			this._contextProps = getSubNodes( this._context );
 
 			await Promise.all(
 				Array.from( this._watchdogs.values() )
@@ -149,9 +171,6 @@ export default class ContextWatchdog extends Watchdog {
 
 	async _setupWatchdog( watchdog ) {
 		watchdog.updateContext( this._context );
-
-		// TODO
-		await watchdog.create();
 	}
 
 	async _destroy( isInternal = false ) {
@@ -161,6 +180,7 @@ export default class ContextWatchdog extends Watchdog {
 			const context = this._context;
 
 			this._context = null;
+			this._contextProps = new Set();
 
 			await Promise.all(
 				Array.from( this._watchdogs.values() )
@@ -173,9 +193,9 @@ export default class ContextWatchdog extends Watchdog {
 	}
 
 	_isErrorComingFromThisInstance( error ) {
-		// TODO
-
-		return areConnectedThroughProperties( this._context, error.context );
+		// Return true only if the error comes directly from the context.
+		// Ignore cases when the error comes from editors.
+		return areConnectedThroughProperties( this._contextProps, error.context );
 	}
 
 	static for( Context, watchdogConfig ) {

+ 1 - 1
packages/ckeditor5-watchdog/src/editorwatchdog.js

@@ -10,7 +10,7 @@
 /* globals console */
 
 import { throttle, cloneDeepWith, isElement } from 'lodash-es';
-import areConnectedThroughProperties from '@ckeditor/ckeditor5-utils/src/areconnectedthroughproperties';
+import areConnectedThroughProperties from './utils/areconnectedthroughproperties';
 import Watchdog from './watchdog';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 

+ 81 - 10
packages/ckeditor5-watchdog/tests/contextwatchdog.js

@@ -3,17 +3,17 @@
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
-/* globals document */
+/* 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;
-	const contextOptions = {};
 	let mainWatchdog;
 
 	beforeEach( () => {
@@ -32,7 +32,7 @@ describe( 'ContextWatchdog', () => {
 	} );
 
 	it.skip( 'case: editor, contextItem', async () => {
-		mainWatchdog = ContextWatchdog.for( Context, contextOptions );
+		mainWatchdog = ContextWatchdog.for( Context, {} );
 
 		mainWatchdog.add( {
 			editor1: {
@@ -52,9 +52,9 @@ describe( 'ContextWatchdog', () => {
 		await mainWatchdog.destroy();
 	} );
 
-	describe( 'case: no editors and contextItems', () => {
+	describe( 'for no items added', () => {
 		it( 'should create only context', async () => {
-			mainWatchdog = ContextWatchdog.for( Context, contextOptions );
+			mainWatchdog = ContextWatchdog.for( Context, {} );
 
 			await mainWatchdog.waitForReady();
 
@@ -64,7 +64,7 @@ describe( 'ContextWatchdog', () => {
 		} );
 
 		it( 'should have proper states', async () => {
-			mainWatchdog = ContextWatchdog.for( Context, contextOptions );
+			mainWatchdog = ContextWatchdog.for( Context, {} );
 
 			expect( mainWatchdog.state ).to.equal( 'initializing' );
 
@@ -76,11 +76,37 @@ describe( 'ContextWatchdog', () => {
 
 			expect( mainWatchdog.state ).to.equal( 'destroyed' );
 		} );
+
+		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;
+
+				const originalErrorHandler = window.onerror;
+				window.onerror = sinon.spy();
+
+				mainWatchdog.on( 'restart', errorSpy );
+
+				setTimeout( () => throwCKEditorError( 'foo', mainWatchdog.context ) );
+
+				await waitCycle();
+
+				sinon.assert.calledOnce( errorSpy );
+
+				expect( mainWatchdog.context ).to.not.equal( oldContext );
+
+				window.onerror = originalErrorHandler;
+			} );
+		} );
 	} );
 
-	describe( 'case: multiple editors', () => {
+	describe( 'for multiple editors', () => {
 		it( 'should allow adding multiple items without waiting', async () => {
-			mainWatchdog = ContextWatchdog.for( Context, contextOptions );
+			mainWatchdog = ContextWatchdog.for( Context, {} );
 
 			mainWatchdog.add( {
 				editor1: {
@@ -106,7 +132,7 @@ describe( 'ContextWatchdog', () => {
 		} );
 
 		it( 'should allow adding and removing items without waiting', async () => {
-			mainWatchdog = ContextWatchdog.for( Context, contextOptions );
+			mainWatchdog = ContextWatchdog.for( Context, {} );
 
 			mainWatchdog.add( {
 				editor1: {
@@ -140,10 +166,47 @@ describe( 'ContextWatchdog', () => {
 
 			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();
+				const originalErrorHandler = window.onerror;
+
+				window.onerror = sinon.spy();
+
+				mainWatchdog.on( 'error', () => {
+					window.onerror = originalErrorHandler;
+				} );
+				mainWatchdog.on( 'restart', restartSpy );
+
+				setTimeout( () => throwCKEditorError( 'foo', mainWatchdog.context ) );
+
+				await waitCycle();
+
+				sinon.assert.calledOnce( restartSpy );
+
+				expect( mainWatchdog.context ).to.not.equal( oldContext );
+
+				window.onerror = originalErrorHandler;
+			} );
+		} );
 	} );
 
 	it( 'case: recreating watchdog', async () => {
-		mainWatchdog = ContextWatchdog.for( Context, contextOptions );
+		mainWatchdog = ContextWatchdog.for( Context, {} );
 
 		await mainWatchdog.destroy();
 		let err;
@@ -165,3 +228,11 @@ describe( 'ContextWatchdog', () => {
 		expect( err.message ).to.match( /Cannot add items do destroyed watchdog\./ );
 	} );
 } );
+
+function throwCKEditorError( name, context ) {
+	throw new CKEditorError( name, context );
+}
+
+function waitCycle() {
+	return new Promise( res => setTimeout( res ) );
+}